member_object_pointer_traits
Yesterday I wrote about const_cast<T>(..) and how to define it in C++. But I did not make it work with data-member pointers because the Boost Type Traits library does not supply traits for data member objects. Here I’ll show you how to write type-traits and supporting template functions for data members. We’ll define the following templates:
- member_object_pointer_traits<T>
- add_member_object_pointer_const<T>
- remove_member_object_pointer_const<T>
- add_member_object_pointer_volatile<T>
- remove_member_object_pointer_volatile<T>
- add_member_object_pointer_cv<T>
- remove_member_object_pointer_cv<T>
We’ll start with the headers.
# include <boost/type_traits.hpp> using boost::is_same; using boost::add_const; using boost::add_volatile; using boost::add_cv; using boost::remove_const; using boost::remove_volatile; using boost::remove_cv; using boost::remove_pointer; # include <boost/mpl/assert.hpp> // BOOST_MPL_ASSERT(..) // BOOST_MPL_ASSERT_NOT(..)
Boost (1_36) already provides these template functions that deal with members.
- is_member_pointer<T>
- is_member_function_pointer<T>
- is_member_object_pointer<T>
Using a similar naming convention we’ll define member_object_pointer_traits<T>. Something like this will probably be part of Boost soon, along with member_function_pointer_traits<T>.
// Declare member_object_pointer_traits but do not
// define a body. The struct will only ever be
// instantiated through specialization.
template< typename DATA_MEMBER_PTR >
struct member_object_pointer_traits;
// Specialized member_object_pointer_traits that
// only matches member-object pointers.
template< typename T, typename C >
struct member_object_pointer_traits< T (C::*) >
{
typedef C class_type;
typedef T data_type;
};
We’d also like to be able to manipulate member-object pointers using functions like these:
- add_const<T>
- remove_const<T>
- add_volatile<T>
- remove_volatile<T>
- add_cv<T>
- remove_cv<T>
- add_pointer<T>
- remove_pointer<T>
But remove_pointer<T> cannot remove the pointer from member-object pointer types because the result is not a valid type. Also, add_const<T> and remove_const<T> cannot add/remove const that is buried in the member-object pointer.
// Define type a_class.
class a_class { public: int a_data_member; };
// Define non-const data-member pointer type.
typedef
int a_class::*
ptr_non_c_member_obj_type; /* new type name */
// Define const data-member pointer type.
typedef
const int a_class::*
ptr_const_member_obj_type; /* new type name */
// Confirm that regular type-traits functions
// do not work with member-object pointer types.
// remove_pointer<T> does nothing
BOOST_MPL_ASSERT( (is_same<
remove_pointer<
ptr_non_c_member_obj_type
>::type,
ptr_non_c_member_obj_type
>));
// remove_const<T> does nothing
BOOST_MPL_ASSERT( (is_same<
remove_const<
ptr_const_member_obj_type
>::type,
ptr_const_member_obj_type
>));
// add_const<T> adds const outside the
// type and does not affect the inner const.
BOOST_MPL_ASSERT( (is_same<
add_const<
ptr_non_c_member_obj_type
>::type,
const
ptr_non_c_member_obj_type
>));
We can define special add/remove functions that work just with member-object pointer types using member_object_pointer_traits<T> which we defined above.
# define DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( PRE, POST ) \
template< typename T > \
struct PRE ## _member_object_pointer_ ## POST \
{ \
typedef member_object_pointer_traits< T > \
traits_type; \
\
typedef typename traits_type::class_type \
class_type; \
\
typedef typename traits_type::data_type \
data_type; \
\
typedef typename PRE ## _ ## POST< data_type >::type \
adjusted_data_type; \
\
/* Construct a member-object-pointer type */ \
/* around the adjusted inner type. */ \
typedef adjusted_data_type (class_type::* \
type); \
}
// remove_member_object_pointer_const<T>
// remove_member_object_pointer_volatile<T>
// remove_member_object_pointer_cv<T>
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( remove, const );
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( remove, volatile);
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( remove, cv );
// add_member_object_pointer_const<T>
// add_member_object_pointer_volatile<T>
// add_member_object_pointer_cv<T>
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( add, const );
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( add, volatile);
DEFINE_MEMBER_OBJECT_POINTER_FUNCTION( add, cv );
# undef DEFINE_MEMBER_OBJECT_POINTER_FUNCTION
// Test some of the above template functions:
BOOST_MPL_ASSERT( (is_same<
remove_member_object_pointer_const<
ptr_const_member_obj_type
>::type,
ptr_non_c_member_obj_type
>));
BOOST_MPL_ASSERT( (is_same<
add_member_object_pointer_const<
ptr_non_c_member_obj_type
>::type,
ptr_const_member_obj_type
>));
And that’s how we can define template functions for working with member-object pointers in the Boost style. The traits for member-function pointers will resemble the function-traits class and will be more complicated.
We’ll use the templates we’ve defined here in the next post, when we extend our version of const_cast<T>(..) to work with member-object-pointers.
Defining const_cast<T>(..) as a templated function
A few posts ago I wrote about cast_away_const(..), a function that removes the top-level const from a variable’s type. Unlike const_cast<T>(..), cast_away_const(..) can be called without specifying the return type as a template argument.
Of course const_cast<T>(..) is part of the C++ language and is provided by the compiler, so you don’t have to #include anything to get it. It’s not supposed to be a function and is not specified in functional terms. Until recently you couldn’t even simulate it as a function.
But that’s no longer true. The template language supported by modern compilers is a lot more sophisticated than it used to be, and you can now define your own const_cast<T>(..) to safely wrap a raw C-style casting. In this post I’ll show you how.
I’ll also argue that since const_cast<T>(..) can be defined as a function, it SHOULD be defined as a function. The implementation and test suite would then become the specification. C++ should be defined in C++ whenever possible.
To define our own const_cast<T>(..), we’ll build on Type Traits and MPL from Boost.
# include <boost/type_traits.hpp> using boost::is_same; using boost::is_const; using boost::is_volatile; using boost::is_pointer; using boost::is_reference; using boost::add_cv; using boost::add_pointer; using boost::add_reference; using boost::remove_cv; using boost::remove_pointer; using boost::remove_reference; # include <boost/mpl/logical.hpp> # include <boost/mpl/int.hpp> using boost::mpl::bool_; using boost::mpl::true_; using boost::mpl::false_; using boost::mpl::and_; using boost::mpl::or_; using boost::mpl::not_; # include <boost/mpl/assert.hpp> // BOOST_MPL_ASSERT(..) // BOOST_MPL_ASSERT_NOT(..)
Let’s start with something simpler, has_const_somewhere<T>, which will give you an idea of how to implement const_cast<T>(..). In the following implementation, has_const_somewhere<T> starts with a type that looks something like int***&, and it peels off the layers, first removing the outer reference and then stripping off the pointers one by one, all the while looking for const. We’ll need something like that for const_cast<T>(..).
// Helper for has_const_somewhere<T>
template< typename T >
struct has_const_somewhere_no_ref
: or_<
is_const< T >, /* stop when we hit const */
and_<
is_pointer< T >,
has_const_somewhere_no_ref<
typename remove_pointer< T >::type
>
>
>
{ };
// has_const_somewhere<T>
template< typename T >
struct has_const_somewhere
: has_const_somewhere_no_ref<
typename remove_reference< T >::type
>
{ };
// Test has_const_somewhere<T>
BOOST_MPL_ASSERT( (has_const_somewhere< const int& >));
BOOST_MPL_ASSERT( (has_const_somewhere< const int***** >));
BOOST_MPL_ASSERT( (has_const_somewhere< const int* const * >));
BOOST_MPL_ASSERT_NOT( (has_const_somewhere< int*****& >));
// Confirm that has_const_somewhere<T> evals to true_ or
// false_, and not just to a type with a bool-looking T::value.
BOOST_MPL_ASSERT( (is_same<
true_,
has_const_somewhere< const int >::type >));
BOOST_MPL_ASSERT( (is_same<
false_,
has_const_somewhere< int**& >::type >));
We’ll define strip_out_cv<T> in a similar way. strip_out_cv<T> removes all the const and volatile markers from a type of the form T*****& by peeling back all the layers and then putting them back again, but without const and volatile. Remember that const_cast<T>(..) deals with volatile as well as const.
// strip_out_cv_no_ref<T> recursive type.
// Removes outer pointer (along with any cv attached),
// removes all the inner cv markers, and puts the outer
// pointer back on again.
template< typename T, bool IS_PTR = is_pointer< T >::value >
struct strip_out_cv_no_ref
: add_pointer<
typename strip_out_cv_no_ref<
// remove_pointer<..> will remove top-level
// const and volatile if they are present.
typename remove_pointer<
T
>::type
>::type
>
{ };
// Specialization when IS_PTR is false
template< typename T >
struct strip_out_cv_no_ref< T, false >
: remove_cv< T >
{ };
// strip_out_cv_maybe_ref<T>
// Strips off the outer ref (&), removes all inner const, and
// puts the outer ref back on again.
template< typename T, bool IS_REF = is_reference< T >::value >
struct strip_out_cv_maybe_ref
: add_reference<
typename strip_out_cv_no_ref<
typename remove_reference<
T
>::type
>::type
>
{ };
// Specialization when there is no outer ref.
template< typename T >
struct strip_out_cv_maybe_ref< T, false >
: strip_out_cv_no_ref< T >
{ };
// strip_out_cv<T>
// Wraps up the above template functions.
template< typename T >
struct strip_out_cv
: strip_out_cv_maybe_ref< T >
{ };
// Test strip_out_cv<T>
# define ASSERT_STRIP_CV( AFTER, BEFORE) \
BOOST_MPL_ASSERT( (is_same< AFTER, \
strip_out_cv< BEFORE >::type > ))
ASSERT_STRIP_CV( int, const int);
ASSERT_STRIP_CV( int, volatile int);
ASSERT_STRIP_CV( int*&,
const volatile int* volatile const &);
ASSERT_STRIP_CV( char****&,
char* const * volatile **&);
ASSERT_STRIP_CV( char******, char******);
ASSERT_STRIP_CV( char******,
volatile char* const * volatile const **** const);
# undef ASSERT_STRIP_CV
Now we’ll define load_with_cv<T> which is the opposite of strip_out_cv<T>. Instead of removing them, load_with_cv<T> adds const and volatile markers to a type whereever possible. load_with_cv< int*& >::type ends up as const volatile int* const volatile &.
// load_with_cv_no_ref<T> recursive type.
// Removes outer pointer, loads up the rest with cv,
// and puts the outer pointer back on again this time
// marked as both const and volatile.
template< typename T, bool IS_PTR = is_pointer< T >::value >
struct load_with_cv_no_ref
: add_cv<
typename add_pointer<
// recursive type definition
typename load_with_cv_no_ref<
typename remove_pointer<
T
>::type
>::type
>::type
>
{ };
// Specialization when T is not a pointer
template< typename T >
struct load_with_cv_no_ref< T, false >
: add_cv< T >
{ };
// load_with_cv_maybe_ref<T>
// Strips off the outer ref (&), marks as both
// const and volatile, and puts the outer ref
// back on again.
template< typename T, bool IS_REF = is_reference< T >::value >
struct load_with_cv_maybe_ref
: add_reference<
typename load_with_cv_no_ref<
typename remove_reference<
T
>::type
>::type
>
{ };
// Specialization when T is not a reference.
template< typename T >
struct load_with_cv_maybe_ref< T, false >
: load_with_cv_no_ref< T >
{ };
// load_with_cv<T>
// Wraps up the above templates.
template< typename T >
struct load_with_cv
: load_with_cv_maybe_ref< T >
{ };
// Test load_with_cv<T>
# define ASSERT_LOAD_CV( BEFORE, AFTER) \
BOOST_MPL_ASSERT( (is_same< AFTER, \
load_with_cv< BEFORE >::type > ))
ASSERT_LOAD_CV( int, const volatile int);
ASSERT_LOAD_CV( int, volatile const int);
ASSERT_LOAD_CV( int**&,
volatile const int*
const volatile *
const volatile &);
// The original type can start with const
// and volatile markers already in it.
ASSERT_LOAD_CV( const volatile int**&,
volatile const int*
const volatile *
const volatile &);
# undef ASSERT_LOAD_CV
Now we have all the tools we need to define our own const_cast<T>(..), which we’ll call const_cast_x<T>(..).
// const_cast_x<T>(..)
// replacement for const_cast<T>(..)
template< typename TRG >
inline
TRG
const_cast_x( typename load_with_cv< TRG >::type a)
{
return (typename strip_out_cv< TRG >::type) a;
}
Yeah! We can test it, and it works, at least with msvc++9.0 and g++3.4.5. All of the following compile with no complaint.
// Test const_cast_x<T>(..)
# ifndef NDEBUG
const int ca = 0;
const int& rca = ca;
const int* const pca = &ca;
const int* const& rpca = pca;
const int*
volatile const * const
ppca = &pca;
volatile const int*
const volatile * const&
rppca = ppca;
int& a1 = const_cast_x< int& >( ca);
int& a2 = const_cast_x< int& >( rca);
int& a3 = const_cast_x< int& >( *pca);
int& a4 = const_cast_x< int& >( *rpca);
int& a5 = const_cast_x< int& >( **ppca);
int& a6 = const_cast_x< int& >( **rppca);
int* pa1 = const_cast_x< int* >( &ca);
int* pa2 = const_cast_x< int* >( &rca);
int* pa3 = const_cast_x< int* >( pca);
int* pa4 = const_cast_x< int* >( rpca);
int* pa5 = const_cast_x< int* >( *ppca);
int* pa6 = const_cast_x< int* >( *rppca);
int** ppa3 = const_cast_x< int** >( &pca);
int** ppa4 = const_cast_x< int** >( &rpca);
int** ppa5 = const_cast_x< int** >( ppca);
int** ppa6 = const_cast_x< int** >( rppca);
int*** pppa5 = const_cast_x< int*** >( &ppca);
int*** pppa6 = const_cast_x< int*** >( &rppca);
# endif /* ifndef NDEBUG */
This proves that it’s possible to write a close approximation to const_cast<T>(..) using templates. But we cannot specify exactly what const_cast<T>(..) is supposed to do by writing it in C++ yet because const_cast_x<T>(..) is not exactly right.
First of all, const_cast<T>(..) can cast away const in a data-member pointer while const_cast_x<T>(..) cannot.
struct MyStruct { int v; };
// const data member
const int MyStruct::*
p_const_data_member = &MyStruct::v;
// no compiler error/warning
int MyStruct::*
p_data_member1 =
const_cast< int MyStruct::* >( p_const_data_member);
// compiler error!
int MyStruct::*
p_data_member2 =
const_cast_x< int MyStruct::* >( p_const_data_member);
I know how to get the const out of const int MyStruct::*, and can fix this inconsistency. This post is already too long though, so I’ll talk about it in another post.
Also consider the following which fail if you use the compiler-supplied const_cast<T>(..) but are OK if you use const_cast_x<T>(..).
# ifndef NDEBUG // These do not work with const_cast<T>(..). int ca1 = const_cast_x< int >( ca); int ca2 = const_cast_x< const int >( ca); const volatile int ca3 = const_cast_x< const int >( ca); # endif /* ifndef NDEBUG */
These fail because casting is not necessary, and because you are only supposed to const_cast<T>(..) pointers, refs, and data-member pointers. const_cast_x<T>(..) could be fixed to work correctly here.
Or consider the following. All of these compile on msvc++9.0, but they all fail on g++3.4.5 (except for rpa6 and rppa6, which I cannot explain). Almost all of these work with both compilers if you use const_cast<..>(..) instead of const_cast_x<..>(..), although a few have r-value problems.
// Test const_cast_x<T>(..) // // All of these work with msvc++9.0. // Most of these fail with g++3.4.5. // These mostly work if you use const_cast<T>(..) // instead of const_cast_x<T>(..). # ifndef NDEBUG int*& rpa1 = const_cast_x< int*& >( &ca); int*& rpa2 = const_cast_x< int*& >( &rca); int*& rpa3 = const_cast_x< int*& >( pca); int*& rpa4 = const_cast_x< int*& >( rpca); int*& rpa5 = const_cast_x< int*& >( *ppca); int*& rpa6 = const_cast_x< int*& >( *rppca); int**& rppa3 = const_cast_x< int**& >( &pca); int**& rppa4 = const_cast_x< int**& >( &rpca); int**& rppa5 = const_cast_x< int**& >( ppca); int**& rppa6 = const_cast_x< int**& >( rppca); int***& rpppa5 = const_cast_x< int***& >( &ppca); int***& rpppa6 = const_cast_x< int***& >( &rppca); # endif /* ifndef NDEBUG */
The r-value types coming in C++0x will let us refine and correct const_cast_x<..>(..)‘s behavior.
In conclusion, once we have r-values we will probably be able to define const_cast<T>(..) exactly as a C++ function, which should be something we strive for.
We should also have a way, maybe with static_assert(..) or #pragma, to improve compiler error and warning messages, such as those warning us of spurious casts or bindings to temporary values.
I also think it should be a goal to define the other casting forms as C++ functions. It’s almost possible now with reinterpret_cast<T>(..) and static_cast<T>(..). We’d need programmer access to the compiler-generated Run-Time Type Information (RTTI) to implement dynamic_cast<T>(..), so that should be a goal in a future version of C++. And of course polymorphic_cast<T>(..), polymorphic_downcast<T>(..), and numeric_cast<T>(..) are already defined as templated functions.
Template programming with MPL — XOR example
The Boost extension libraries are a treasure trove of brilliant hackery. The template usage and metaprogramming throughout the libraries is particularly interesting. But it’s also confusing, especially when you first see it. The C++ template language is like a functional language with limited types and obtuse syntax. It has no loops and allows no side effects (no changes to objects or variables after construction). It uses the constructs of C++ in unexpected ways and is very different from normal C++ programming.
The best way to get an idea of how template programming works is to look at it. So here are some examples of how to implement a very simple template function, an XOR predicate that takes two boolean arguments and returns a third.
As a base we start with the templates in type_traits which are included in TR1. We will also use the Boost MPL library, which provides predicates like and_<..>, or_<..>, and not_<..>. MPL also provides the boolean template class bool_<..> and it’s two type-values true_ and false_.
# include <boost/type_traits.hpp> using boost::is_same; // is_same< A, B > is a template function // that compares types A and B. # include <boost/mpl/logical.hpp> using boost::mpl::bool_; using boost::mpl::true_; using boost::mpl::false_; using boost::mpl::and_; using boost::mpl::or_; using boost::mpl::not_; # include <boost/mpl/assert.hpp> // BOOST_MPL_ASSERT(..) and // BOOST_MPL_ASSERT_NOT(..)
In template programming, you assign variable values with typedef. true_ and false_ are variables that evaluate to bool<true> and bool<false>. You can assign them over and over again as long as the value does not change. Typedef variables never change once they are assigned, so they’re not really “variable”. They are constant variables (which sounds like an oxymoron). There is no untypedef.
// true_ is already assigned, but you can // assign it over and over again to an // identical value. typedef bool_<true> true_; typedef bool_<true> true_; typedef bool_<true> true_; // not_<false_> is the same as true_. typedef not_<false_>::type true_;
You can use is_same< A, B > to test if two types are the same. Boost MPL provides a useful ASSERT.
BOOST_MPL_ASSERT( (is_same< true_, true_ >)); BOOST_MPL_ASSERT_NOT( (is_same< true_, false_ >)); BOOST_MPL_ASSERT( (is_same< true_, bool_<true> >));
true_ and false_ are self evaluating. You evaluate an expression or a variable by appending ::type.
// true_, false_, bool_<true>, etc are self evaluating. BOOST_MPL_ASSERT( (is_same< true_, true_::type >)); BOOST_MPL_ASSERT( (is_same< true_, true_::type::type >)); // not_<false_> is an expression. // Tt only yields _true after you evaluate it. BOOST_MPL_ASSERT_NOT( (is_same< true_, not_<false_> >)); BOOST_MPL_ASSERT( (is_same< true_, not_<false_>::type >)); BOOST_MPL_ASSERT( (is_same< true_, or_<true_>::type >));
You may have noticed BOOST_MPL_ASSERT( (..)) has double parentheses around its argument. This is needed because the argument is usually an expression containing commas, and the preprocessor will mis-interpret the commas unless they are buried in parentheses.
You might be tempted by the following macros. But they only work with type expressions that contain no commas.
# define ASSERT_same_types( A, B) \
BOOST_MPL_ASSERT( (is_same< B , A >))
# define ASSERT_different_types( A, B) \
BOOST_MPL_ASSERT_NOT( (is_same< B , A >))
ASSERT_same_types( int, int);
ASSERT_different_types( char, int);
ASSERT_same_types( void(*)(int), void(*)(int) );
ASSERT_different_types( false_, true_::type::type);
// These do not work because of the commas!
// Putting parens around the args doesn't work either.
/*
ASSERT_same_types( true_, and_<true_,true_>::type >));
ASSERT_same_types( int(int,char), int(int,char) >));
ASSERT_same_types( (int(int,char)), (int(int,char)) >));
*/
Notice that and_<..> evaluates its arguments while is_same<..> does not. (This is not strictly true since and_<..> short circuits, but in these examples we can treat it like it evaluates all its args.)
// is_same<..> does not eval its args. Otherwise // you could not use it with types like int. BOOST_MPL_ASSERT( (is_same< int, int >)); // These look the same, but they're not because args // are not evaluated. BOOST_MPL_ASSERT_NOT( (is_same< true_, not_<false_> >)); // You can force evaluation. Only the second arg needs // to be eval'ed in this case. BOOST_MPL_ASSERT( (is_same< true_, not_<false_>::type >)); // Here we force eval of the outer not_<..> because it is an // arg to is_same<..>. But the args to and_<..>, or_<..>, // and not_<..> are automatically evaluated. BOOST_MPL_ASSERT( (is_same< true_, not_<or_<and_<not_<false_>>>>::type >)); // You can force evaluation of args to and_<..> etc, but // the results will just be eval'ed again. Since the // results are self evaluating (true_ or false_), you can // redundantly evaluate them several times and the // expressions will still work. BOOST_MPL_ASSERT( (is_same< true_, not_<or_<and_<not_<false_>::type>::type::type>>::type >));
Now we can finally get to our example template predicate. Here is a definition of xor_test<..> that evals its args. It can be used with MPL functions like and_<..> and or_<..>.
template< typename A, typename B >
struct xor_test
: bool_< A::type::value ^ B::type::value >
{ };
BOOST_MPL_ASSERT_NOT( (xor_test< true_, true_ >));
BOOST_MPL_ASSERT_NOT( (xor_test< false_, false_ >));
BOOST_MPL_ASSERT( (xor_test< true_, false_ >));
BOOST_MPL_ASSERT( (xor_test< false_, true_ >));
BOOST_MPL_ASSERT( (xor_test< false_, or_<true_> >));
I don’t like using the bitwise XOR operator ^ here with boolean args, although it works. I’d prefer to use a boolean operator like ^^ if it existed. Also, I considered replacing : bool< A::type::value ^ B::type::value > with : bool< A::value ^ B::value > because X::value also evaluates X. It works with and_<..>, or_<..> and not_<..> because these classes mimic bool_<..> and provide value as well as type.
When you define an MPL-style template function, you need a typedef XX type; in the template struct to evaluate the template expression. You get that if you inherit from bool_<..>, or you can typedef XX type; yourself.
You also need to figure out if the predicate should evaluate true_ or false_. You can specify that with logic or with template specialization.
The first XOR predicate example inherits from bool_<..> and calculates true/false with and_<..>, or_<..>, and not_<..>.
// xor_1<..> inherits from bool_<..>
template< typename A, typename B >
struct xor_1
: or_< and_< A, not_< B > >, and_< not_< A >, B > >::type
{ };
BOOST_MPL_ASSERT_NOT( (xor_1< true_, true_ >));
BOOST_MPL_ASSERT_NOT( (xor_1< false_, false_ >));
BOOST_MPL_ASSERT( (xor_1< true_, false_ >));
BOOST_MPL_ASSERT( (xor_1< false_, true_ >));
BOOST_MPL_ASSERT( (xor_1< false_, or_<true_> >));
BOOST_MPL_ASSERT( (xor_1< or_<true_>, false_ >));
You don’t have to evaluate the supertype of xor_1<..>. You could say or_<..> instead of or_<..>::type. This stops evaluation of the top-level or_<..>, and it means we are not inheriting directly from bool_<..> but rather from or_<..>. But we still evaluate to a bool_<..> type.
The second XOR example typedefs type explicitly. It also typedefs value_type and tag and defines value and operator bool() (the same as operator value_type()). These make it look more like bool_<..> and work better with MPL.
// xor_2<..> typedefs type instead of inheriting
template< typename A, typename B >
struct xor_2
{
typedef
or_< and_< A, not_< B > >, and_< not_< A >, B > >
type;
typedef bool value_type;
static const value_type value = type::value;
operator value_type() { return value; }
typedef bool_<value> tag;
};
BOOST_MPL_ASSERT_NOT( (xor_2< true_, true_ >));
BOOST_MPL_ASSERT_NOT( (xor_2< false_, false_ >));
BOOST_MPL_ASSERT( (xor_2< true_, false_ >));
BOOST_MPL_ASSERT( (xor_2< false_, true_ >));
BOOST_MPL_ASSERT( (xor_2< false_, or_<true_> >));
BOOST_MPL_ASSERT( (xor_2< or_<true_>, false_ >));
The third example inherits from either true_ or false_ and uses specialization to choose which. Template specialization tends to leave parameters un-evaluated. We wrap the non-evalating base class so we can evaluate explicitly.
// specialization and inheritance
template< typename A, typename B >
struct xor_3_no_eval
: false_
{ };
template< >
struct xor_3_no_eval< true_, false_ >
: true_
{ };
template< >
struct xor_3_no_eval< false_, true_ >
: true_
{ };
template< typename A, typename B >
struct xor_3
: xor_3_no_eval< typename A::type, typename B::type >::type
{ };
BOOST_MPL_ASSERT_NOT( (xor_3< true_, true_ >));
BOOST_MPL_ASSERT_NOT( (xor_3< false_, false_ >));
BOOST_MPL_ASSERT( (xor_3< true_, false_ >));
BOOST_MPL_ASSERT( (xor_3< false_, true_ >));
BOOST_MPL_ASSERT( (xor_3< false_, or_<true_> >));
BOOST_MPL_ASSERT( (xor_3< or_<true_>, false_ >));
The last example predicate typedefs explicitly instead of inheriting from bool_<..>. It also uses a recursive type definition.
// specialization and calculation
template< typename A, typename B >
struct xor_4_no_evalB
{
// We only ever get here if B evals to something
// other than true_ or false_. Maybe we should
// flag this:
// BOOST_STATIC_ASSERT( false);
typedef false_ type;
};
template< typename A >
struct xor_4_no_evalB< A, false_ >
{
// recursive type definition uses xor_4_no_evalB<..>
typedef
typename xor_4_no_evalB< not_< A >, true_ >::type
type;
};
template< typename A >
struct xor_4_no_evalB< A, true_ >
{
typedef
typename not_< A >::type
type;
};
template< typename A, typename B >
struct xor_4
: xor_4_no_evalB< A, typename B::type >::type
{
typedef bool value_type;
static const value_type value = type::value;
operator value_type() { return value; }
typedef bool_< value > tag;
};
BOOST_MPL_ASSERT_NOT( (xor_4< true_, true_ >));
BOOST_MPL_ASSERT_NOT( (xor_4< false_, false_ >));
BOOST_MPL_ASSERT( (xor_4< true_, false_ >));
BOOST_MPL_ASSERT( (xor_4< false_, true_ >));
BOOST_MPL_ASSERT( (xor_4< false_, or_<true_> >));
BOOST_MPL_ASSERT( (xor_4< or_<true_>, false_ >));
So there you have it — five examples of a very simple template XOR predicate.