【问题标题】:void_t and trailing return type with decltype: are they completely interchangeable?void_t 和带有 decltype 的尾随返回类型:它们完全可以互换吗?
【发布时间】:2017-01-25 21:38:37
【问题描述】:

考虑以下基于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

template<typename T>
auto func() -> decltype(std::declval<T>().foo(), void()) { }

我想到的所有例子都是如此。我在找到一个void_t或尾随返回类型的情况下无法使用decltype,而在其对方不能。
最复杂的情​​况可以通过尾随返回类型和重载的组合来解决(例如,当 检测器 用于在两个函数之间切换而不是作为触发来禁用或启用某些东西)。

是这样吗?它们(void_tdecltype 作为尾随返回类型加上重载,如果需要)完全可以互换吗?
否则,在什么情况下无法使用它来解决约束而我不得不使用特定的方法?

【问题讨论】:

  • 第一行的typename = void_t&lt;&gt;可以是typename = void,imo比较清楚一点。
  • This 可能会引起您的兴趣
  • @W.F.它使用decltype 代替void_t 作为特化的模板参数。我问的是稍微不同的东西。还是谢谢你。
  • @skypjack 我没有说这是重复的,我只是说它可能会引起你的兴趣:)
  • Yakk 的can_apply 在阅读您的问题时立即浮现在脑海

标签: c++ templates sfinae decltype c++17


【解决方案1】:

这相当于元编程:我应该写一个函数还是只写内联代码。喜欢编写类型特征的原因与喜欢编写函数的原因相同:它更自文档化、可重用、更易于调试。喜欢编写尾随 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&lt;T&gt;::value。您不能编写结尾的-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 ) { ... }

【讨论】:

  • 那么,不存在一种可以用另一种不能的情况吗?这只是方便的问题吗?确实有道理,感谢您的详细回答。
  • @skypjack static_assert?
  • 触摸。如果我没有至少两次阅读答案,我不应该在晚上发表评论!对不起。
  • @Jarod42 通过尾随返回类型编写类型特征似乎是一种尴尬的方式,使用void_tcan_apply/is_detected 会更简洁。
【解决方案2】:

当我实现自己的自制版本的 Concepts Lite 时,我同时使用了 void_t 和尾随 decltype(顺便说一句,我成功了),这需要创建许多额外的类型特征,其中大多数以一种或另一种方式使用检测习语.我使用了 void_t,尾随 decltype 和前面的 decltype。

据我了解,这些选项在逻辑上是等价的,因此理想的、100% 一致的编译器应该使用所有选项产生相同的结果。然而,问题是特定编译器可能(并且将)在不同情况下遵循不同的实例化模式,其中一些模式可能超出内部编译器限制。例如,当我试图让 MSVC 2015 更新 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 和其他版本的以下版本中的问题。

【讨论】:

  • declval&lt;T&amp;&gt;() 产生一个左值。 (此外,可以分配许多 xvalues。)
猜你喜欢
  • 1970-01-01
  • 2011-11-07
  • 2017-08-02
  • 1970-01-01
  • 2019-09-09
  • 2017-10-08
  • 2019-01-26
  • 2011-04-14
  • 2023-03-04
相关资源
最近更新 更多