这些特征都是 值特征,继承自 integral_constant 并提供一个简单的 true
或 false
布尔值 value
,它反映了给定类型是否可以与给定的运算符一起使用。
例如,has_plus<int, double>::value
是一个 bool
类型的值,其值为 true
,因为可以将 double
类型添加到 int
类型,如下面的代码所示
int i; double d; i+d;
也可以知道运算符的结果是否可以用作给定类型的函数参数。例如,has_plus<int, double, float>::value
是 true
,因为可以将 double
类型添加到 int
类型,并且结果 (double
) 可以转换为 float
参数,如下面的代码所示
void f(float) { }; int i; double d; f(i+d);
这些特征可用于优化支持给定操作的类型的代码。例如,一个函数 std::advance
,用于将迭代器增加给定步数,可以如下实现
#include <boost/type_traits/has_plus_assign.hpp> namespace detail { template < class Iterator, class Distance, bool has_plus_assign > struct advance_impl; // this is used if += exists (efficient) template < class Iterator, class Distance > struct advance_impl<Iterator, Distance, true> { void operator()(Iterator &i, Distance n) { i+=n; } }; // this is use if += does not exists (less efficient but cannot do better) template < class Iterator, class Distance > struct advance_impl<Iterator, Distance, false> { void operator()(Iterator &i, Distance n) { if (n>0) { while (n--) ++i; } else { while (n++) --i; } } }; } // namespace detail template < class Iterator, class Distance > inline void advance(Iterator &i, Distance n) { detail::advance_impl< Iterator, Distance, ::boost::has_plus_assign<Iterator>::value >()(i, n); }
然后,编译器根据类型执行 +=
运算的能力选择最有效的实现
#include <iostream> class with { int m_i; public: with(int i=0) : m_i(i) { } with &operator+=(int rhs) { m_i+=rhs; return *this; } operator int const () { return m_i; } }; class without { int m_i; public: without(int i=0) : m_i(i) { } without &operator++() { ++m_i; return *this; } without &operator--() { --m_i; return *this; } operator int const () { return m_i; } }; int main() { with i=0; advance(i, 10); // uses += std::cout<<"with: "<<i<<'\n'; without j=0; advance(j, 10); // uses ++ std::cout<<"without: "<<j<<'\n'; return 0; }
语法如下
template< class Rhs, class Ret=dont_care >
has_op; // prefix operator template< class Lhs, class Ret=dont_care >
has_op; // postfix operator template< class Lhs, class Rhs=Lhs, class Ret=dont_care >
has_op; // binary operator
其中
Lhs
是在 operator op
左侧使用的类型,Rhs
是在 operator op
右侧使用的类型,Ret
是我们想要知道 operator op
的结果是否可以转换为的类型。默认行为 (Ret=dont_care
) 是不检查运算符的返回值。如果 Ret
与默认值 dont_care
不同,则检查返回值是否可转换为 Ret
。可转换为 Ret
意味着返回值可以用作期望 Ret
的函数的参数
void f(Ret); Lhs lhs; Rhs rhs; f(lhs+rhs); // is valid if has_plus<Lhs, Rhs, Ret>::value==true
如果 Ret=void
,则检查返回类型是否完全为 void
。
下表给出了支持的二元、前缀和后缀运算符的列表。
表 1.6. 支持的二元运算符
二元运算符 |
特征名称 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
以下运算符由于无法使用相同的技术实现而不被支持:operator=
, operator->
, operator&
, operator[]
, operator,
, operator()
, operator new
。
运算符参数中的引用符号 &
被忽略,因此 has_plus< int&, double& >::value==has_plus< int, double >::value
。之所以选择这样做,是因为如果以下代码有效(无效)
int i; double d; i+d;
则以下代码也有效(无效)
int &ir=i; double &dr=d; ir+dr;
无法正确处理 volatile
限定符,因此任何使用此限定符的构造都具有未定义的行为。
作为帮助,下表给出了每个特征模板参数的必要条件,以使特征 value
为 true
。它们是非充分条件,因为条件必须对所有参数和返回类型都为 true
,value
才能为 true
。
表 1.7. 运算符参数为真的必要非充分条件
运算符声明 |
|
|
|
---|---|---|---|
|
false |
true |
true |
|
false |
true |
true |
|
false |
true |
false |
|
false |
true |
true |
表 1.8. 运算符返回类型为真的必要非充分条件
运算符声明 |
|
|
|
|
|
---|---|---|---|---|---|
|
true |
false |
false |
false |
false |
|
false |
true |
true |
false |
true |
|
false |
true |
true |
false |
true |
|
false |
true |
true |
true |
true |
|
false |
true |
true |
false |
true |
实现仅包含头文件。应首先包含以下头文件
#include <boost/type_traits/has_operator.hpp>
或
#include <boost/type_traits/has_op.hpp>
其中 op
是为所需运算符选择的文本名称。第一种方法包含所有运算符特征。
所有特征都以相同的方式实现,使用预处理器宏来避免代码重复。主要文件位于 boost/type_traits/detail
中:has_binary_operator.hpp
、has_prefix_operator.hpp
和 has_postfix_operator.hpp
。
给定一个充分符合 C++11 标准的编译器,这些特征以相当紧凑和直接的方式实现,应该始终给出准确的答案。
在 C++03 中,有一个遗留实现,例如,下面展示了前缀 operator-
namespace boost { namespace detail { // This namespace ensures that argument-dependent name lookup does not mess things up. namespace has_unary_minus_impl { // 1. a function to have an instance of type T without requiring T to be default // constructible template <typename T> T &make(); // 2. we provide our operator definition for types that do not have one already // a type returned from operator- when no such operator is // found in the type's own namespace (our own operator is used) so that we have // a means to know that our operator was used struct no_operator { }; // this class allows implicit conversions and makes the following operator // definition less-preferred than any other such operators that might be found // via argument-dependent name lookup struct any { template <class T> any(T const&); }; // when operator- is not available, this one is used no_operator operator-(const any&); // 3. checks if the operator returns void or not // conditions: Rhs!=void // we first redefine "operator," so that we have no compilation error if // operator- returns void and we can use the return type of // (-rhs, returns_void_t()) to deduce if operator- returns void or not: // - operator- returns void -> (-rhs, returns_void_t()) returns returns_void_t // - operator- returns !=void -> (-rhs, returns_void_t()) returns int struct returns_void_t { }; template <typename T> int operator,(const T&, returns_void_t); template <typename T> int operator,(const volatile T&, returns_void_t); // this intermediate trait has member value of type bool: // - value==true -> operator- returns void // - value==false -> operator- does not return void template < typename Rhs > struct operator_returns_void { // overloads of function returns_void make the difference // yes_type and no_type have different size by construction static ::boost::type_traits::yes_type returns_void(returns_void_t); static ::boost::type_traits::no_type returns_void(int); static const bool value = sizeof(::boost::type_traits::yes_type)==sizeof(returns_void((-make<Rhs>(),returns_void_t()))); }; // 4. checks if the return type is Ret or Ret==dont_care // conditions: Rhs!=void struct dont_care { }; template < typename Rhs, typename Ret, bool Returns_void > struct operator_returns_Ret; template < typename Rhs > struct operator_returns_Ret < Rhs, dont_care, true > { static const bool value = true; }; template < typename Rhs > struct operator_returns_Ret < Rhs, dont_care, false > { static const bool value = true; }; template < typename Rhs > struct operator_returns_Ret < Rhs, void, true > { static const bool value = true; }; template < typename Rhs > struct operator_returns_Ret < Rhs, void, false > { static const bool value = false; }; template < typename Rhs, typename Ret > struct operator_returns_Ret < Rhs, Ret, true > { static const bool value = false; }; // otherwise checks if it is convertible to Ret using the sizeof trick // based on overload resolution // condition: Ret!=void and Ret!=dont_care and the operator does not return void template < typename Rhs, typename Ret > struct operator_returns_Ret < Rhs, Ret, false > { static ::boost::type_traits::yes_type is_convertible_to_Ret(Ret); // this version is preferred for types convertible to Ret static ::boost::type_traits::no_type is_convertible_to_Ret(...); // this version is used otherwise static const bool value = sizeof(is_convertible_to_Ret(-make<Rhs>()))==sizeof(::boost::type_traits::yes_type); }; // 5. checks for operator existence // condition: Rhs!=void // checks if our definition of operator- is used or an other // existing one; // this is done with redefinition of "operator," that returns no_operator or has_operator struct has_operator { }; no_operator operator,(no_operator, has_operator); template < typename Rhs > struct operator_exists { static ::boost::type_traits::yes_type check(has_operator); // this version is preferred when operator exists static ::boost::type_traits::no_type check(no_operator); // this version is used otherwise static const bool value = sizeof(check(((-make<Rhs>()),make<has_operator>())))==sizeof(::boost::type_traits::yes_type); }; // 6. main trait: to avoid any compilation error, this class behaves // differently when operator-(Rhs) is forbidden by the standard. // Forbidden_if is a bool that is: // - true when the operator-(Rhs) is forbidden by the standard // (would yield compilation error if used) // - false otherwise template < typename Rhs, typename Ret, bool Forbidden_if > struct trait_impl1; template < typename Rhs, typename Ret > struct trait_impl1 < Rhs, Ret, true > { static const bool value = false; }; template < typename Rhs, typename Ret > struct trait_impl1 < Rhs, Ret, false > { static const bool value = ::boost::type_traits::ice_and< operator_exists < Rhs >::value, operator_returns_Ret < Rhs, Ret, operator_returns_void < Rhs >::value >::value >::value ; }; // specialization needs to be declared for the special void case template < typename Ret > struct trait_impl1 < void, Ret, false > { static const bool value = false; }; // defines some typedef for convenience template < typename Rhs, typename Ret > struct trait_impl { typedef typename ::boost::remove_reference<Rhs>::type Rhs_noref; typedef typename ::boost::remove_cv<Rhs_noref>::type Rhs_nocv; typedef typename ::boost::remove_cv< typename ::boost::remove_reference< typename ::boost::remove_pointer<Rhs_noref>::type >::type >::type Rhs_noptr; static const bool value = trait_impl1 < Rhs_noref, Ret, ::boost::is_pointer< Rhs_noref >::value >::value; }; } // namespace impl } // namespace detail // this is the accessible definition of the trait to end user template < typename Rhs, typename Ret=::boost::detail::has_unary_minus_impl::dont_care > struct has_unary_minus : ::boost::integral_constant<bool,(::boost::detail::has_unary_minus_impl::trait_impl < Rhs, Ret >::value)> { }; } // namespace boost
T
的私有成员,则相应特征的实例化将产生编译器错误。因此,这些特征不能用于确定类型是否具有公共运算符。struct A { private: A operator-(); }; boost::has_unary_minus<A>::value; // error: A::operator-() is private
A
存在,并且 B
可转换为 A
,则存在问题。在这种情况下,编译器将报告歧义重载,因为现有运算符和我们提供的运算符(使用 any
类型的参数)都需要类型转换,因此没有一个是首选的。struct A { }; void operator-(const A&); struct B { operator A(); }; boost::has_unary_minus<A>::value; // this is fine boost::has_unary_minus<B>::value; // error: ambiguous overload between // operator-(const any&) and // operator-(const A&) // both need type conversion
struct B { }; struct A { A(const B&) { } }; void operator-(const A&); boost::has_unary_minus<A>::value; // this is fine boost::has_unary_minus<B>::value; // error: ambiguous overload between // operator-(const any&) and // operator-(const A&) // both need type conversion
true
而不是 false
。这尤其适用于标准库的容器和 operator==
。例如#include <boost/type_traits/has_equal_to.hpp> #include <iostream> template <class T> struct contains { T data; }; template <class T> bool operator==(const contains<T> &lhs, const contains<T> &rhs) { return f(lhs.data, rhs.data); } class bad { }; class good { }; bool f(const good&, const good&) { return true; } int main() { std::cout<<std::boolalpha; // works fine for contains<good> std::cout<<boost::has_equal_to< contains< good > >::value<<'\n'; // true contains<good> g; g==g; // ok // does not work for contains<bad> std::cout<<boost::has_equal_to< contains< bad > >::value<<'\n'; // true, should be false contains<bad> b; b==b; // compile time error return 0; }
volatile
限定符未得到正确处理,可能导致未定义的行为Frederic Bron 非常感谢 boost 邮件列表中的许多人的友好帮助和耐心。特别是,以下人员对实现非常有帮助:Edward Diener, Eric Niebler, Jeffrey Lee Hellrung (Jr.), Robert Stewart, Roman Perepelitsa, Steven Watanabe, Vicente Botet。