繁体   English   中英

void_t和带有decltype的尾随返回类型:它们是完全可以互换的吗?

[英]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_tdecltype作为尾随返回类型加上如果需要的重载)完全可以互换吗?
否则,是什么情况下,一个人不能用来解决约束,我被迫使用一个特定的方法?

这是元编程的等价物:我应该编写一个函数,还是应该只编写内联代码。 更喜欢编写类型特征的原因与更喜欢编写函数的原因相同:它更自我记录,可重用,更容易调试。 更喜欢编写尾随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); }

0int/...有点奇怪,对吗?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM