[英]void_t and trailing return type with decltype: are they completely interchangeable?
考慮以下基於void_t
基本示例:
template<typename, typename = void_t<>>
struct S: std::false_type {};
template<typename T>
struct S<T, void_t<decltype(std::declval<T>().foo())>>: std::true_type {};
它可以如下使用:
template<typename T>
std::enable_if_t<S<T>::value> func() { }
使用尾隨返回類型和decltype
可以完成相同的decltype
:
template<typename T>
auto func() -> decltype(std::declval<T>().foo(), void()) { }
對於我想到的所有例子都是如此。 我找不到一個案例,其中可以使用void_t
或帶有decltype
的尾隨返回類型,而它的對應物不能。
最復雜的情況可以通過尾隨返回類型和重載的組合來解決(例如,當檢測器用於在兩個函數之間切換而不是作為觸發器來禁用或啟用某些東西時)。
是這樣的嗎? 它們( void_t
和decltype
作為尾隨返回類型加上如果需要的重載)完全可以互換嗎?
否則,是什么情況下,一個人不能用來解決約束,我被迫使用一個特定的方法?
這是元編程的等價物:我應該編寫一個函數,還是應該只編寫內聯代碼。 更喜歡編寫類型特征的原因與更喜歡編寫函數的原因相同:它更自我記錄,可重用,更容易調試。 更喜歡編寫尾隨decltype的原因類似於更喜歡編寫內聯代碼的原因:它是一次性的,不能重復使用,所以為什么要把它分解出來並為它提出一個合理的名稱?
但是,為什么你可能想要一個類型特征,這里有很多原因:
假設我有一個特點,我想多次檢查。 像fooable
一樣。 如果我寫一次類型特征,我可以將其視為一個概念:
template <class, class = void>
struct fooable : std::false_type {};
template <class T>
struct fooable<T, void_t<decltype(std::declval<T>().foo())>>
: std::true_type {};
現在我可以在很多地方使用相同的概念:
template <class T, std::enable_if_t<fooable<T>{}>* = nullptr>
void bar(T ) { ... }
template <class T, std::enable_if_t<fooable<T>{}>* = nullptr>
void quux(T ) { ... }
對於檢查多個表達式的概念,您不希望每次都重復它。
伴隨着重復,組成兩種不同類型的特征很容易:
template <class T>
using fooable_and_barable = std::conjunction<fooable<T>, barable<T>>;
編寫兩個尾隨返回類型需要寫出所有兩個表達式...
使用類型特征,很容易檢查類型是否不滿足特征。 那只是!fooable<T>::value
。 您不能編寫trailing- decltype
表達式來檢查某些內容是否無效。 當您有兩個不相交的重載時,可能會出現這種情況:
template <class T, std::enable_if_t<fooable<T>::value>* = nullptr>
void bar(T ) { ... }
template <class T, std::enable_if_t<!fooable<T>::value>* = nullptr>
void bar(T ) { ... }
這很好地導致......
假設我們有一個短類型特征,用類型特征標記調度更加清晰:
template <class T> void bar(T , std::true_type fooable) { ... }
template <class T> void bar(T , std::false_type not_fooable) { ... }
template <class T> void bar(T v) { bar(v, fooable<T>{}); }
比起其他情況:
template <class T> auto bar(T v, int ) -> decltype(v.foo(), void()) { ... }
template <class T> void bar(T v, ... ) { ... }
template <class T> void bar(T v) { bar(v, 0); }
0
和int/...
有點奇怪,對嗎?
static_assert
如果我不想在一個概念上使用SFINAE,而只是想用一個明確的消息來努力失敗怎么辦?
template <class T>
struct requires_fooability {
static_assert(fooable<T>{}, "T must be fooable!");
};
當(如果?)我們得到概念時,顯然實際上使用概念在涉及元編程的所有事情上都要強大得多:
template <fooable T> void bar(T ) { ... }
當我實現自己的自制的Concepts Lite版本(順便說一句,我成功)時,我使用了void_t和尾隨decltype,這需要創建許多其他類型特征,其中大多數都以某種方式使用Detection慣用法。 我使用了void_t,尾隨decltype和前面的decltype。
據我所知,這些選項在邏輯上是等價的,因此一個理想的,符合100%條件的編譯器應該使用所有這些選項生成相同的結果。 然而,問題是特定編譯器可能(並且將會)在不同情況下遵循不同的實例化模式,並且這些模式中的一些可能超出內部編譯器限制。 例如,當我嘗試使MSVC 2015 Update 2 3檢測到相同類型的乘法時,唯一有效的解決方案是在decltype之前:
template<typename T>
struct has_multiplication
{
static no_value test_mul(...);
template<typename U>
static decltype(*(U*)(0) *= std::declval<U>() * std::declval<U>()) test_mul(const U&);
static constexpr bool value = !std::is_same<no_value, decltype(test_mul(std::declval<T>())) >::value;
};
每個其他版本都會產生內部編譯器錯誤,盡管其中一些版本與Clang和GCC一起工作正常。 我還必須使用 *(U*)(0)
而不是declval
,因為連續使用三個declval
雖然完全合法,但在這種特殊情況下對於編譯器來說只是很多。
我的壞,我忘記了。 實際上我使用了*(U*)(0)
因為declval
產生了類型的rvalue-ref,它不能被賦值,這就是我使用它的原因。 但其他一切仍然有效,這個版本適用於其他人沒有的。
所以現在我的答案是:“只要你的編譯器認為它們是”,它們就是相同的“。 這是你必須通過測試找到的東西。 我希望這將在以下版本的MSVC和其他版本中不再成為問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.