提问人:KamilCuk 提问时间:9/12/2023 最后编辑:Jan SchultkeKamilCuk 更新时间:10/11/2023 访问量:1590
为类型特征_t别名和_v变量模板的目的是什么?
What is the purpose of _t aliases and _v variable templates for type traits?
问:
还有很多 和 后缀,如 、 和 milions 等其他此类函数。*_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
答:
问题在于,当您拥有 时,是一个依赖名称,因此需要对其进行限定,以便通知编译器它是类型而不是对象的名称。这些名称是别名,因此您不必自己键入。std::result_of<TEMPLATE_STUFF>::type
type
typename
type
*_t
typename trait_name::type
typename
s 不需要 the,因为这些是值,但它保持一致的语法,并且仍然较少键入。typename
*_v
在任何上下文中公开实现细节(如 std::result_of::type 或 std::is_same::value)是否有用?
它们不是实现细节。这些是要使用的原始接口。
变体必须实现为仅自 C++11 以来存在的别名模板,并且变体必须实现为仅自 C++14 以来存在的变量模板。_t
_v
因此,最初根本无法定义没有间接 through 或成员的接口。速记版本是后来添加的,除了安全一些键入并使代码更易于阅读之外,什么也没做。::type
::value
这些变体可以直接用 C++11 添加,但很早就已经考虑将类型特征包含在 C++11 中。据推测,别名模板是后来添加到草稿中的,或者接口已经很熟悉,因为类型特征实现在 C++11 之前也已经存在。_t
::type
如果您正在编写不需要与早期 C++ 版本兼容的 C++ 或更高版本的代码,则(几乎)没有理由不使用 and 变体而不是旧的 / 接口。如果你需要支持较旧的 C++ 版本,那么你应该相应地使用 /,直到分别达到 C++11 和 C++14。_t
_v
::type
::value
::type
::value
在这种情况下,偶尔使用类型特征本身而不是成员是有用的。由于类型特征被定义为派生自或根据其计算方式派生,因此这允许将逻辑保持在类型级别,而不会走任何弯路。::value
::value
std::true_type
std::false_type
有时,在模板中仅有条件地使用 / 成员也很有用。在这种情况下,它们的优点是,只有当 / 出现在实例化的上下文中时,才需要实例化类型特征。因为实例化可能会导致许多其他实例化,具体取决于类型特征,并且如果类型尚未完成,则可能具有 UB。::value
::type
::value
::type
为了保持一致性,新添加的类型特征仍然遵循相同的方案。
评论
_v
std::conjunction
/std::disjunction
there is no reason to not use the _t and _v variants instead of the old ::type/::value interface
总的来说,这就是我正在寻找的答案。但是,我想知道,如果总是首选 C++17,那么它有什么意义呢?可以删除吗?std::invoke_result
std::invoke_result_t
std::invoke_result
TL;DR - 别名可以显著缩短元编程代码,因为省略了 和 。 后来添加了变量模板,以便与别名对称,因为它们在各个方面都更好。_t
::type
typename
_v
_t
C++17 - 变量模板_v
以下是将变量模板引入 C++17 的提案文件中的一句话:_v
变量模板(如 is_same_v<T、U> 优于嵌套模板 像 is_same<T、U>::value 这样的常量有几个原因:
显然,前者短了 5 个字符。
不太明显的是,前者消除了“不连贯的冗长”。在处理类型特征时,通常不得不说这样的话 enable_if_t<!is_same<X, decay_t>::value, Z>, 其中 “is_same” 和 “::value”之间可能有大量的 机械。
不得不说“::value”不是一个功能,它是已经解决的语言限制的解决方法。(请注意,此提案 不触及结构模板,所以想要 is_same类型(而不是其嵌套常量)不受影响。
此外,采用文件中的要点:
随着类型特征别名模板的成功,已经提出了一组匹配的变量模板。对变量模板的语言支持来得太晚了,无法自信地对 C++14 进行此更改,但此后的经验表明,这种变量模板更加简洁,可以以类似于别名通过标准被广泛采用的方式清理文本,并且作者在他自己的标准类型特征库实现中使用它们的经验是,使用此类变量编写代码时要简单得多模板,而不是将值转换为类型,然后对类型执行模板操作,然后再将类型转换回值。
对标准的影响是,许多引用的地方会改用 .节省的费用不如别名模板那么大,因为没有令人讨厌的删除。然而,使用和指代特征,而不是用来提取意义的一致性是令人信服的。_t
_v
_t
some_trait<T>::value
some_trait_v<T>
typename
_t
_v
::something
C++14 - 别名_t
在C++14中引入别名的论文中提供了类似的推理,并增加了NathanOliver在他的回答中评论的额外好处。引用论文:_t
typename
不幸的是,对于最常见的用例来说,上述灵活性是有代价的。 在模板上下文中,C++ 要求对元函数的每个“元调用”都具有语法开销 以介绍性关键字的形式,以及后缀:
typename
::type
typename metafunction-name<metafunction-argument(s)>::type
即使是相对简单的构图也会很快变得有些混乱;更深 嵌套是彻头彻尾的笨拙:
template< class T > using reference_t = typename conditional<is_reference<T>::value, T, typename add_lvalue_reference<T>::type>::type;
更糟糕的是,意外省略关键字可能会导致对程序员来说晦涩难懂的诊断 谁不擅长元编程细节。
根据我们的经验,传递元函数(而不是元数据)构成了相对 一小部分元功能组成。我们发现自己传递了元函数结果 更频繁。因此,我们建议为库添加一组模板别名,以减轻程序员表达更多内容的负担 常见情况。请注意,在上述示例的以下重写中,没有任何关键字,也没有任何后缀,因此将语句从 3 压缩到 2 代码行数:
TransformationTraits
typename
::type
template< class T > using reference_t = conditional_t< is_reference<T>::value, T, add_lvalue_reference_t<T> >;
别名模板在 C++14 中引入,变量模板在 C++17 中引入。这些存在的原因有很多。_t
_v
和模板更方便。_t
_v
首先,五个字符是否比 .此外,您需要在后者前面加上前缀,因为编译器无法推断是类型成员还是静态成员。另请参阅我必须在哪里以及为什么必须放置“模板”和“typename”关键字?。trait_t<T>
trait<T>::type
typename
::type
比较 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();
和模板提供了更大的实现自由度。_t
_v
特征都是类这一事实是一个重大限制。这意味着每次使用例如 将不得不实例化一个新的类模板,而这相对昂贵。现代编译器将所有类型特征实现为内部函数,类似于:std::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);
请参阅 libc++ 中的 __type_traits/is_same.h
。
该类显然是多余的,直接通过变量模板使用内置函数会更有效。
特征类现在已经没有意义了吗?_t
_v
答案取决于编译器。 支持这些类的一个常见论点是它们允许短路。 例如,您可以替换
(std::is_same_v<Ts, int> && ...)
// with
std::conjunction_v<std::is_same<Ts, int>...>
...与折叠表达式不同,并非所有表达式都会被实例化。
然而,至少对于 clang 和 GCC 来说,实例化的成本非常便宜(由于它是内置的),即使折叠表达式不会短路,使用它们仍然会提高编译速度。std::is_same
std::is_same_v
但是,较旧的编译器可能会使用实际类而不是内置类来实现某些特征,因此理论上短路可能会更好。
特征类有时对 TMP 更方便。
无论性能如何,这些特征有时对元编程都很有用,例如:
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>;
};
在某些情况下,从 trait 类继承很方便,尽管不是绝对必要的。
另见
您可以在以下论文中找到别名/变量模板的基本原理和进一步解释:_t
_v
评论
_t
_t
are sometimes useful for metaprogramming, such as:
澄清一下,不可能写 using ?或者生成的代码是不可读的?我能够写这个 godbolt.org/z/oMn5EsMbY,它看起来还不错,但我不知道如何正确匹配。我的观点是,如果你可以用形式来表达一切,或者反过来,为什么同时拥有两个接口?简明扼要是一个论点。Select<Pair<A, B>
template<...> using Select_t = ...
template<std::pair T>
_t
::type
::first_type
::second_type
std::pair
::first_type
::second_type
评论
::type
::value
T