C++ void_t和带有decltype的尾部返回类型:它们完全可以互换吗?
考虑以下基于C++ void_t和带有decltype的尾部返回类型:它们完全可以互换吗?,c++,templates,sfinae,decltype,c++17,C++,Templates,Sfinae,Decltype,C++17,考虑以下基于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 {}; 我想到的所有例子都是如此。我未能找到一个案例,其中可以
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 {};
我想到的所有例子都是如此。我未能找到一个案例,其中可以使用void\u t
或带有decltype
的尾部返回类型,而其对应项不能使用。最复杂的情况可以通过尾部返回类型和重载的组合来解决(例如,当检测器用于在两个功能之间切换而不是作为禁用或启用某个功能的触发器时) 是这样吗?它们(
void\u t
和decltype
作为尾部返回类型加上重载(如果需要)是否完全可以互换?否则,在什么情况下不能使用一个函数来绕过约束,而我被迫使用特定的方法?这是元编程的等价物:我应该写函数还是只内联写代码。喜欢编写类型特征的原因与喜欢编写函数的原因相同:它更具有自文档性、可重用性,更易于调试。喜欢编写尾部decltype的原因与喜欢内联编写代码的原因类似:它是一次性的,不可重用,所以为什么要努力将它分解出来,并为它取一个合理的名称呢 但以下是你想要类型特征的一系列原因:
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>{}); }
重复
假设我有一个特点,我想检查很多次。像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 {};
这很好地引出
标签发送
假设我们有一个简短的类型特征,那么用类型特征标记dispatch就更清楚了:
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 <fooable T> void bar(T ) { ... }
模板空栏(T){…}
我在实现自己自制的Concepts Lite版本(顺便说一句,我成功了)时使用了void_t和trailing decltype,这需要创建许多额外的类型特征,其中大多数以某种方式使用检测习惯用法。我使用了void\u 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;
};
模板
结构具有_乘法
{
静态无值测试(mul)(…);
模板
静态decltype(*(U*)(0)*=std::declval()*std::declval())test_mul(const U&);
静态constexpr bool value=!std::is_same::value;
};
其他版本都会产生内部编译器错误,尽管其中一些可以很好地处理Clang和GCC。我还必须使用*(U*)(0)
而不是declval
,因为在一行中使用三个declval
,虽然完全合法,但在这种特殊情况下,对编译器来说太多了
我的错,我忘了。实际上,我使用了*(U*)(0)
,因为declval
生成类型为的右值ref,这是无法分配的,这就是为什么我使用这个。但是其他的一切都是有效的,这个版本在其他版本没有的地方起作用
所以现在我的答案是:“它们是相同的,只要编译器认为它们是一样的。”。这是你必须通过测试来发现的。我希望这将不再是MSVC和其他版本中的一个问题。
typename=void\u t
第一行可以是typename=void
,这在我看来更清楚。可能是您的问题interest@W.F.它使用decltype
代替void\t
作为专门化的模板参数。相反,我的要求略有不同。无论如何谢谢你。@skypjack我没有说这是一个复制品,我只是说它可能会引起你的兴趣:)雅克的可以应用
在阅读你的问题时会立即浮现在脑海中。所以,不存在任何一种情况下一个可以使用,另一个不能?这只是一个方便的问题吗?这确实有道理,谢谢你的详细回答。@skypjackstatic\u assert
?Touché。如果我没有至少读两遍答案,我就不应该在晚上发表评论!!抱歉。@Jarod42通过尾随返回类型编写类型特征似乎是一种笨拙的方式,如果void\u t
或can\u apply
/被检测到,则会更加简洁。
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); }
template <class T>
struct requires_fooability {
static_assert(fooable<T>{}, "T must be fooable!");
};
template <fooable T> void bar(T ) { ... }
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;
};