标准是否有任何理由将其指定为模板struct
而不是简单的布尔constexpr
?
在一个可能在主要问题的答案中得到回答的另一个问题中,如何用非结构版本进行enable_if
填充?
答案 0 :(得分:18)
一个原因是constexpr
函数无法提供嵌套的type
成员,这在某些元编程情况下很有用。
为了说清楚,我不是只讨论生成类型的转换特征(如make_unsigned
),显然不能成为constexpr
函数。 所有类型特征提供这样的嵌套type
成员,甚至一元类型特征和二元类型特征。例如,is_void<int>::type
为false_type
。
当然,这可以与std::integral_constant<bool, the_constexpr_function_version_of_some_trait<T>()>
一起使用,但实际上并不实际。
在任何情况下,如果你真的想要类似函数的语法,那就已经可以了。您可以使用traits构造函数并利用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;
答案 1 :(得分:4)
注意:这最终看起来更像是一个咆哮,而不是一个正确的答案......虽然我确实得到了一些痒,但请原谅我;)
首先,类特征在历史上是使用模板结构完成的,因为它们优先于constexpr
和decltype
。如果没有这两个,使用函数会有更多的工作,尽管is_base_of
的各种库实现在内部使用函数来获得正确的继承。
使用函数的优点是什么?
typename ::type
看起来很愚蠢 TM )实际上,继承可能是对抗类特征的要点。你需要将所有派生类专门用于 momma ,这很烦人。很烦人。使用函数只需继承特征,如果你想,可以专门设置。
缺点是什么?
当然,有人可能会说这实际上很烦人:专攻iterator_traits
,你常常无偿地从std::iterator_traits
继承来获取默认值。不同的功能可以自然地提供这一点。
可以吗?
好吧,总而言之,一切都是基于constexpr
的,除了enable_if
(但后来,它不是特质),你会去:
template <typename T>
typename enable_if<std::is_integral(T()) and
std::is_signed(T())>::type
注意:我在这里没有使用std::declval
因为它需要一个未评估的上下文(即大多数是sizeof
或decltype
)。因此,一个额外的要求(不是立即可见)是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
而您不是t使用类型的值。
如果我需要链接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
,大量特征刚刚变为冗余。
<强> DRY 强>!
继承正常运作!
采用基本的类层次结构:
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
的特征不直接表示true
或false
,而是取其祖先的行为。这样,如果祖先改变了姿势,整个层次结构将自动跟随。自从基本功能由祖先提供以来,大部分时间都遵循其特征是有意义的。类型特征更是如此。
// function version
constexpr bool some_trait(...) { return false; }
constexpr bool some_trait(Base const&) { return true; }
注意:省略号的使用是故意的,这是全能的重载。模板函数比其他重载更好匹配(不需要转换),而省略号总是最差的匹配,保证它只选择那些没有其他重载适合的那些。
我认为没有必要确定后一种方法的简洁程度如何?你不仅可以摆脱template <>
的混乱,而且还可以免费获得继承权。
可以
enable_if
实施吗?
不幸的是,我不这么认为,但正如我已经说过的那样:这不是一个特质。 std
版本与constexpr
很好地兼容,因为它使用bool
参数,而不是类型:)
那么为什么?
嗯,唯一的技术原因是代码的很大一部分已经依赖于历史上作为类型(std::numeric_limit
)提供的一些特征,因此一致性将决定它。
此外,它使boost::is_*
的迁移变得如此简单!
我个人认为这很不幸。但我可能比普通公司更愿意回顾我写的现有代码。
答案 2 :(得分:3)
一个原因是type_traits提案比constexpr提案旧。
另一个是,如果需要,您可以为自己的类型添加特化。
答案 3 :(得分:2)
可能是因为boost已经有了一个用模板实现的type_traits版本。
我们都知道标准委员会中有多少人复制了这一点。
答案 4 :(得分:2)
我认为主要原因是type_traits
已经是tr1
的一部分,因此基本上保证以大致相同的形式结束标准,因此它早于{{1} }。其他可能的原因是:
constexpr
)定义remove_pointer
而不是type
,因此必须以这种方式表达。 value
可以是部分专业化的,而功能则不能,因此可能会使某些特征更容易实现对于你的第二个问题:由于templated structs
定义了一个enable_if
(或者,如果它被传递为false),type
中的嵌套typedef确实是要走的路