【问题标题】:Why are type_traits implemented with specialized template structs instead of constexpr?为什么 type_traits 使用专门的模板结构而不是 constexpr 来实现?
【发布时间】:2012-01-17 14:49:32
【问题描述】:

有什么理由为什么标准将它们指定为模板structs 而不是简单的布尔值constexpr

在另一个可能会很好地回答主要问题的问题中,enable_if 将如何处理非结构版本的内容?

【问题讨论】:

  • "为什么不用简单的 X 来完成?" “X怎么可能做到?”卢兹。 :)
  • enable_if 是一个返回类型的元函数。您不能从常规函数返回类型。只有产生整数常量的类型特征才能成为constexpr 函数。
  • 恐怕我不明白您通过“simple boolean constexpr”提出的替代方案。你如何建议,比如说,std::is_integral 可以被声明但不是?我怀疑答案与以下事实有关:可以在 true_typefalse_type 上重载函数,但不可能在 constexpr 的值上重载函数,而这种重载提供了一个额外的工具对于 TMP。但我不确定——如果你想知道为什么is_integral 等是类模板,那么我看不出它们怎么可能是其他任何东西(除了内置运算符)。
  • "如何使用非结构版本执行 enable_if 内容?" - 完全正确。

标签: c++ c++11 template-meta-programming typetraits


【解决方案1】:

一个原因是constexpr 函数不能提供嵌套的type 成员,这在某些元编程情况下很有用。

为了清楚起见,我说的不仅仅是转换特征(如make_unsigned),它们产生类型并且显然不能成为constexpr 函数。 所有类型特征都提供了这样一个嵌套的type 成员,甚至一元类型特征二元类型特征。例如is_void<int>::typefalse_type

当然,这可以通过std::integral_constant<bool, the_constexpr_function_version_of_some_trait<T>()> 解决,但不太实用。

无论如何,如果你真的想要类似函数的语法,那已经是可能的了。您可以只使用特征构造函数并利用constexpr implicit conversion in integral_constant

static_assert(std::is_void<void>(), "void is void; who would have thunk?");

对于转换特征,您可以使用模板别名来获得接近该语法的内容:

template <bool Condition, typename T = void>
using enable_if = typename std::enable_if<Condition, T>::type;
// usage:
// template <typename T> enable_if<is_void<T>(), int> f();
//
// make_unsigned<T> x;

【讨论】:

  • 实际上,一个函数可以产生一个类型。例如,通常会检查基到派生的关系:您生成函数的两个重载,具有不同的返回类型,并使用此返回类型。
  • ... grrr ... 讨厌冻结... 无论如何:decltype 允许从函数中轻松获取类型:decltype(is_void(std::declval&lt;int&gt;()))typename is_void&lt;int&gt;::type... 两者看起来非常相似我。
  • 哇模板别名比我最初想象的更有表现力!
  • 您的最后一个示例现在存在于 stdlib 中,为 std::enable_if_t,以及其他类型特征的各种其他等效助手。
【解决方案2】:

注意:这最终看起来更像是一个咆哮而不是一个正确的答案......虽然我确实在阅读之前的答案时感到有些痒,所以请原谅我;)

首先,类特征在历史上是使用模板结构完成的,因为它们早于 constexprdecltype。如果没有这两个,使用函数就需要更多的工作,尽管is_base_of 的各种库实现必须在内部使用函数来获得正确的继承。

使用函数有哪些优势

  • 继承就行了。
  • 语法可以更自然(typename ::type 看起来很蠢 TM
  • 很多特征现在已经过时了

实际上,继承可能是类特征的主要观点。像momma 一样,你需要专门化你的所有派生类,这很烦人。很烦人。使用函数,您只需继承特征,并且可以专门化如果您愿意

有什么缺点

  • 包装!一个结构特征可以同时嵌入多个类型/常量。

当然,有人可能会说这实际上很烦人:专门化iterator_traits,你只是经常无偿继承std::iterator_traits只是以获得默认值。不同的功能会很自然地提供这一点。

可以吗?

好吧,总而言之,除了enable_if 之外,所有内容都将基于constexpr(但是,这不是一个特征),你会去:

template <typename T>
typename enable_if<std::is_integral(T()) and
                   std::is_signed(T())>::type

注意:我没有在这里使用std::declval,因为它需要一个未评估的上下文(即sizeofdecltype 主要是)。所以一个额外的要求(不是立即可见的)是T 是默认可构造的。

如果你真的想要,有一个技巧:

#define VALUE_OF(Type_) decltype(std::declval<T>())

template <typename T>
typename enable_if<std::is_integral(VALUE_OF(T)) and
                   std::is_signed(VALUE_OF(T))>::type

如果我需要一个类型,而不是常量怎么办?

decltype(common_type(std::declval<T>(), std::declval<U>()))

我也没有发现问题(是的,我在这里使用declval)。但是......传递类型与constexpr无关; constexpr 函数在返回您感兴趣的 时很有用。当然可以使用返回复杂类型的函数,但它们不是 constexpr 并且您不使用该值类型。

如果我需要链接 trais 和类型怎么办?

具有讽刺意味的是,这正是函数大放异彩的地方:)

// class version
template <typename Container>
struct iterator { typedef typename Container::iterator type; };

template <typename Container>
struct iterator<Container const> {
  typedef typename Container::const_iterator type;
};

template <typename Container>
struct pointer_type {
  typedef typename iterator<Container>::type::pointer_type type;
};


template <typename Container>
typename pointer_type<Container>::type front(Container& c);

// Here, have a cookie and a glass of milk for reading so far, good boy!
// Don't worry, the worse is behind you.


// function version
template <typename Container>
auto front(Container& c) -> decltype(*begin(c));

什么!骗子!没有定义特征!

嗯...实际上,这就是重点。使用decltype,大量特征刚刚变得冗余

干燥

继承有效!

采用基本的类层次结构:

struct Base {};
struct Derived: Base {};
struct Rederived: Derived {};

并定义一个特征:

// class version
template <typename T>
struct some_trait: std::false_type {};

template <>
struct some_trait<Base>: std::true_type {};

template <>
struct some_trait<Derived>: some_trait<Base> {}; // to inherit behavior

template <>
struct some_trait<Rederived>: some_trait<Derived> {};

注意: Derived 的特征不直接声明 truefalse,而是从其祖先那里获取行为。这样,如果祖先改变立场,整个层次结构就会自动跟随。大多数时候,由于基本功能是由祖先提供的,因此遵循其特征是有意义的。类型特征更是如此。

// function version
constexpr bool some_trait(...) { return false; }

constexpr bool some_trait(Base const&) { return true; }

注意:省略号的使用是有意的,这是包罗万象的重载。模板函数将比其他重载更好地匹配(无需转换),而省略号始终是最差匹配,以确保它仅选择其他重载不适合的函数。

我想没有必要精确说明后一种方法有多简洁?您不仅可以摆脱template &lt;&gt; 的混乱,还可以免费获得继承。

enable_if可以这样实现吗?

不幸的是,我不这么认为,但正如我已经说过的:这不是一个特质。而std 版本与constexpr 配合得很好,因为它使用bool 参数,而不是类型:)

那么为什么

嗯,唯一的技术原因是大部分代码已经依赖于过去作为类型 (std::numeric_limit) 提供的许多特征,因此一致性将决定它。

此外,它使从boost::is_* 的迁移变得如此简单!

我个人认为这很不幸。但我可能比一般公司更渴望审查我编写的现有代码。

【讨论】:

  • 我不太明白您的论点,即“继承仅适用于”基于 constexpr 的函数。介意用一个例子扩展答案吗?
  • @Xeo:完成(接近答案的结尾)。现在它真的开始看起来像无休止的咆哮:p
  • 我明白了。但是,这意味着将您感兴趣的类型作为函数参数而不是模板参数传递,因此您需要到处使用std::declval,并希望不对上下文进行评估。
  • @Xeo:可能是的。它确实明显改变了您使用类型特征的方式。例如,如果您想检测template &lt;typename T&gt; void foo(T const&amp; t),那么您可以使用:template &lt;typename T&gt; auto foo(T const&amp; t) -&gt; typename std::enable_if&lt;some_trait(t)&gt;::type。不需要未评估的上下文,您已经拥有参数:)
  • 我知道已经过了一年,但我最近意识到与这个问题相关的一些事情(我们都还在发现 C++11,对吗?)并来编辑我的答案并看到了你的.. . “很多特征刚刚变得多余。”这可能是真的,也可能不是,但我不认为给出的例子是一个好的例子。 iterator_traits 仍然不是多余的,因为那里的类型不必与自然的 decltype 对应物完全匹配。
【解决方案3】:

一个原因是 type_traits 提案比 constexpr 提案更早。

另一个是如果需要,您可以为自己的类型添加特化。

【讨论】:

  • 可以为自己的类型添加新的 constexpr 函数重载。
  • @rubenvb:可以添加类模板的部分特化,但不能添加函数模板。
  • FTR,&lt;type_traits&gt; 中唯一可以在程序中显式特化的模板是common_type
【解决方案4】:

可能是因为 boost 已经有了一个使用模板实现的 type_traits 版本。

我们都知道标准委员会中有多少人复制提升。

【讨论】:

  • “复制 boost”听起来很傻,而创建 boost 的唯一原因是为标准库的新东西提供测试平台。将其中的内容“复制”到标准库中就是 boost 的目的。请参阅here(PDF 链接)。
  • 当同一个人同时是 Boost 和标准委员会的成员时,您不必“复制”。 :-)
  • @sbi:您链接到的 PDF 并未表明这是“唯一原因”。事实上,它非常清楚地表明,任何给定的库都可能不会被标准库吸收,但有朝一日可能会发生传统的吸收。
  • @Lightness:Boost 是由图书馆工作组的成员发起的,目的是收集图书馆成为现有的实践,并提出一些标准化的建议。很可能 Beman 的原始论文并没有非常明确地说明这一点(我已经有十年没有读过它了),但这并不能改变这是真的事实。我确定在 boost 的文档(或他们的常见问题解答,如果他们有这样的东西)中的某个地方已经详细说明了这一点。
  • @sbi:可能是这样,但我只是指出您的引用无效。 :) (十年没读过的时候引用它似乎有点奇怪!)
【解决方案5】:

我想说主要原因是type_traits 已经是tr1 的一部分,因此基本上可以保证以或多或少相同的形式出现在标准中,因此它早于constexpr。其他可能的原因是:

  • 将特征作为类型允许在特征类型上重载函数
  • 许多特征(如remove_pointer)定义了type 而不是value,因此它们必须以这种方式表达。为定义值的特征和定义类型的特征使用不同的接口似乎是不必要的
  • templated structs 可以部分特化,而函数不能,这样可能会使某些特征的实现更容易

对于你的第二个问题:enable_if 定义了一个 type(或者不是,如果它被传递为 false),一个嵌套在 struct 内的 typedef 确实是要走的路

【讨论】:

  • FTR,&lt;type_traits&gt; 中唯一可以在程序中显式特化的模板是common_type
  • @Grizzly:函数不能部分特化,但可以重载。您可以只使用函数的返回类型,而不是具有嵌套特征...我不明白第一点。
  • @MatthieuM.: 使用函数的返回类型(它不能真正返回任何东西)意味着总是使用 decltype 来声明类型的特征,这看起来很丑陋。我确实意识到函数可以被覆盖,但是这对定义特征有什么帮助,因为所有参数都是每个定义类型而不是值。你的意思是你没有理解第一点? type_traitstr1 中,因此早于 constexpr
  • @Grizzly:我说的是“将特征作为类型允许在特征类型上重载函数”。至于特征,您可以使用std::declval&lt;T&gt;() 来获取一个值(在未评估的上下文中),因此您可以调用false_type foo(T)。但是当然切换到 constexpr 可能意味着你有constexpr bool foo(T) { return false; }如果需要,转换回类型std::bool_type&lt;foo(T)&gt;。当您假设特征仅适用于类型时,您正在逆转工作,一旦您采用 constexpr 方式,这种黑客就会消失:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-10
  • 1970-01-01
  • 2021-07-13
  • 1970-01-01
  • 2013-06-16
  • 1970-01-01
  • 2021-06-12
相关资源
最近更新 更多