Kam*_*Cuk 16 c++ language-design type-traits c++14 c++17
有很多和*_v后缀*_t,例如std::is_same_v、std::invoke_result_t和数result_of_t以百万计的其他此类函数。
它们为什么存在?std::result_of::type在任何情况下公开诸如或 之类的实现细节是否有益std::is_same::value?忽略标准合规性,_v _t版本是否应该始终是首选?难道这些::type ::value版本根本就不存在吗?
Yks*_*nen 21
TL;DR -_t由于省略::type和 ,别名可以显着缩短元编程代码typename。_v后来添加了变量模板,以与_t别名对称,并且因为它们在各方面都更好。
_v变量模板以下是提案论文中将变量模板引入_vC++17 的引用:
\n\n像 is_same_v<T, U> 这样的变量模板优于像 is_same<T, U>::value 这样的嵌套\n常量,原因如下:
\n\n
\n- \n
显然,前者少了5个字符。
\n- \n
不太明显的是,前者消除了“不连贯的冗长”。使用类型特征时,通常必须说类似\nenable_if_t<!is_same<X, Decay_t>::value, Z>,其中“is_same”和\n“::value”由可能存在大量\n机器。
\n- \n
不得不说“::value”不是一个功能,它是已解决的语言限制的解决方法。(请注意,此提议不会触及结构模板,因此想要 nis_same 类型而不是其嵌套常量的元程序员不受影响。)
\n
此外,采用文件中的要点:
\n\n\n\n
_t在类型特征的别名模板取得成功之后,_v已经提出了一组匹配的变量模板。_t对变量模板的语言支持来得太晚,无法自信地对 C++14 进行此更改,但此后的经验表明,此类变量模板更加简洁,可以以与别名已被广泛采用的类似方式清理文本标准,以及作者在自己的标准类型特征库实现中使用它们的经验是,直接使用此类变量模板编写代码要简单得多,而不是将值转换为类型,然后对其执行模板操作类型,然后再将类型转回值。
\n对标准的影响是许多引用的地方some_trait<T>::value会改为使用some_trait_v<T>. 节省的空间并不像别名模板那么大,因为没有令人厌烦的内容typename需要删除。然而,使用_t和_v指代特征而不是用来::something提取含义的一致性是引人注目的。
_t别名在 C++14 中引入别名的论文中提供了类似的推理_t,并添加了额外的好处typename,NathanOliver 在他的回答中评论道。引用论文:
\n\n不幸的是,上述灵活性对于最常见的用例来说是有代价的。\n在模板上下文中,C++ 要求元函数的每个 \xe2\x80\x9cmetacall\xe2\x80\x9d 承担语法开销\n介绍性
\ntypename关键字以及后缀::type:Run Code Online (Sandbox Code Playgroud)\ntypename metafunction-name<metafunction-argument(s)>::type\n即使是相对简单的构图也会很快变得有些混乱;更深的\n嵌套是非常笨拙的:
\nRun Code Online (Sandbox Code Playgroud)\ntemplate< class T > using reference_t\n = typename conditional<is_reference<T>::value, T,\n typename add_lvalue_reference<T>::type>::type;\n更糟糕的是,意外地省略关键字可能会导致诊断对于不熟悉元编程细节的程序员来说是晦涩难懂的。\n
\n根据我们的经验,传递元函数(而不是元数据)构成元函数组合中相对较小的一部分。我们发现自己传递元函数结果的频率要高得多。因此,我们建议为库\xe2\x80\x99s\n 添加一组模板别名,
\nTransformationTraits以减轻程序员表达这种更为常见的情况的负担。请注意,在上面示例的以下重写中,没有任何typename\n关键字,也没有任何::type后缀,从而将语句从 3 行压缩为 2\n 行代码:Run Code Online (Sandbox Code Playgroud)\ntemplate< class T > using reference_t\n = conditional_t< is_reference<T>::value, T, add_lvalue_reference_t<T> >;\n
Nat*_*ica 17
问题是,当您有std::result_of<TEMPLATE_STUFF>::type, 时,type是一个从属名称,因此需要进行限定,typename以便通知编译器这type是类型的名称而不是对象的名称。这些*_t名称是别名,typename trait_name::type因此您不必typename自己输入。
stypename不需要,*_v因为它们是值,但它保持一致的语法并且仍然更少的打字。
在任何上下文中公开 std::result_of::type 或 std::is_same::value 等实现细节是否有益?
它们不是实施细节。这些是打算使用的原始接口。
变_t体必须实现为仅自 C++11 以来存在的别名模板_v,并且变体必须实现为自 C++14 以来仅存在的变量模板。
因此,最初根本不可能在没有通过::type或::value成员间接访问的情况下定义接口。速记版本是后来添加的,除了安全一些打字并使代码更易于阅读之外没有任何作用。
这些_t变体本来可以直接添加到 C++11 中,但类型特征很早就被考虑纳入 C++11 中。据推测,别名模板是后来添加到草案中的,或者::type接口已经很熟悉了,因为类型特征实现在 C++11 之前就已存在。
如果您正在编写不需要与早期 C++ 版本兼容的 C++17 或更高版本的代码,则(几乎)没有理由不使用 和_t变_v体而不是旧的::type/::value接口。如果您需要支持旧的 C++ 版本,那么您应该相应地使用::type/ ::value,直到分别达到 C++11 和 C++14。
有时::value使用类型特征本身而不是::value成员很有用。因为类型特征被定义为派生自std::true_type或std::false_type根据其评估方式,这允许人们将逻辑保持在类型级别而不需要任何弯路。
有时,仅在模板中有条件地使用::value/::type成员也很有用。::value在这种情况下,它们的优点是仅当/::type出现在实例化上下文中时才需要实例化类型特征。因为根据类型特征,实例化可能会导致许多其他实例化,并且如果类型尚未完成,则可能会出现 UB。
为了保持一致性,新添加的类型特征仍然遵循相同的方案。
别名_t模板是在 C++14 中引入的,_v变量模板是在 C++17 中引入的。这些存在的原因有很多。
_t模板_v更方便。首先,trait_t<T>比 短五个字符trait<T>::type。此外,您需要为后者添加前缀,typename因为编译器无法推断::type是类型还是静态成员。另请参阅在哪里以及为什么必须放置“template”和“typename”关键字?。
比较 C++11/C++17 代码,这可以产生很大的差异:
// C++17
template <typename T>
std::enable_if_t<!std::is_void_v<T>> foo();
// C++11
template <typename T>
typename std::enable_if<!std::is_void<T>::value>::type foo();
Run Code Online (Sandbox Code Playgroud)
_t了_v更多的实施自由度。事实上,特征都是类别,这是一个很大的限制。这意味着每次使用egstd::is_same都必须实例化一个新的类模板,这是相对昂贵的。现代编译器将所有类型特征实现为内在函数,类似于:
template <typename _A, typename _B>
struct is_same {
static constexpr bool value = __is_same(_A, _B);
};
template <typename _A, typename _B>
inline constexpr bool is_same_v = __is_same(_A, _B);
Run Code Online (Sandbox Code Playgroud)
请参阅__type_traits/is_same.hlibc++。
该类显然是多余的,直接通过变量模板使用内置函数会高效得多。
_t并且_v存在了吗?答案取决于编译器。支持这些类的一个常见论点是它们允许短路。例如,您可以替换
(std::is_same_v<Ts, int> && ...)
// with
std::conjunction_v<std::is_same<Ts, int>...>
Run Code Online (Sandbox Code Playgroud)
...与折叠表达式不同,并非所有内容std::is_same都会被实例化。然而,至少对于 clang 和 GCC 来说,实例化的成本std::is_same_v非常便宜(由于它是内置的),即使折叠表达式不会短路,但使用它们仍然可以提高编译速度。
然而,较旧的编译器可能使用实际的类而不是内置类来实现一些特征,因此理论上短路可能会更好。
无论性能如何,这些特征有时对于元编程很有用,例如:
template <typename T>
struct Select;
template <typename A, typename B>
struct Select<Pair<A, B>> : std::conditional<LeftIsBetter<A, B>, A, B> {};
// is more concise than
template <typename A, typename B>
struct Select<Pair<A, B>> {
using type = std::conditional_t<LeftIsBetter<A, B>, A, B>;
};
Run Code Online (Sandbox Code Playgroud)
在某些情况下,从特征类继承很方便,尽管不是绝对必要的。
您可以在以下论文中找到别名/变量模板_t的基本原理和进一步说明:_v