Previously I covered the topic of SFINAE as a means of using compile-time information provided by the C++ language’s overload resolution mechanism to make decisions at compile-time. In the context of TR1, specifically the reference_wrapper class, we can use SFINAE as a means of determining various properties that the reference_wrapper class is required to provide depending on the type being wrapped.
reference_wrapper is a parameterized wrapper around a reference to a type T. It is a copy constructible and assignable class, thus enabling it to be changed even after creation, unlike standard references. reference_wrapper is also defined as inheriting from std::unary_function or std::binary_function, depending on if particular conditions are met (which will be detailed below). It also has what is known as a weak result_type; ergo the result_type is defined only if the type T is a function, reference to a function, pointer to a function type, member function pointer, or a class type with a result_type type member. In all other cases, result_type will not be defined.
Conditional Inheritance
The reference_wrapper<T> class is defined as inheriting from std::unary_function if the following conditions are met:
- If the type T is a function pointer or a function type that takes only one argument, hereafter called T1, and returning a result, hereafter called R.
- If the type T is a possibly cv-qualified member function pointer to a member function such that T1 is defined as cv T* taking no arguments, and returning a result R.
- If the type T is derived from std::unary_function<T1, R>.
Obviously the first two of these conditions are fairly easy to detect, using template specialization, however the last one requires a bit more work to detect, since we need to detect if T has derived from a templated class. For our purposes we’ll define a template-structure named is_unary_function which will be used as our base class for determining if we should inherit from std::unary_function.
Clearly the first step should be to detect the simplest cases first, that of the unary function type and unary function pointer type, so let us declare our template class type, and specialize it for the two function cases.
template<class T> struct is_unary_function;
template<class Arg, class Result>
struct is_unary_function<Result (Arg)> : true_type {
typedef std::unary_function<Arg, Result> unary_function_type;
};
template<class Arg, class Result>
struct is_unary_function<Result (*)(Arg)> : true_type {
typedef std::unary_function<Arg, Result> unary_function_type;
};
The next step should be to specialize the is_unary_function template for pointer to member function types. However, we must also specialize it for the various cv-qualified member function pointer types that exist. There are four such types, non-cv-qualified, const qualified, volatile qualified, and const volatile qualified.
template<class Class, class Result>
struct is_unary_function<Result (Class::*)()> : true_type {
typedef std::unary_function<Class*, Result> unary_function_type;
};
template<class Class, class Result>
struct is_unary_function<Result (Class::* const)()> : true_type {
typedef std::unary_function<Class const*, Result> unary_function_type;
};
template<class Class, class Result>
struct is_unary_function<Result (Class::* volatile)()> : true_type {
typedef std::unary_function<Class volatile*, Result> unary_function_type;
};
template<class Class, class Result>
struct is_unary_function<Result (Class::* const volatile)()> : true_type {
typedef std::unary_function<Class const volatile*, Result> unary_function_type;
};
The final step is to identify types that inherit from std::unary_function<T1, R>. For this purpose, we will use SFINAE, but we'll also require another template to correctly build the unary_function_type typedef.
template<class T>
struct has_unary_base {
private:
template<class Arg, class Result>
static sfinae_types::one test(std::unary_function<Arg, Result>*);
static sfinae_types::two test(...);
public:
static const bool value = sizeof(test((T*)0)) == sizeof(sfinae_types::one);
};
template<class T, bool B> struct unary_base_typedef {
typedef empty unary_function_type;
};
template<class T> struct unary_base_typedef<T, true> {
typedef std::unary_function<
typename T::argument_type,
typename T::result_type> unary_function_type;
};
template<class T>
struct is_unary_function : integral_type<bool, has_unary_base<T>::value> {
typedef typename unary_base_typedef<T>::unary_function_type unary_function_type;
};
Clearly this template serves a dual purpose. It can be used to both determine if an object is a unary function type, and it also provides us with the appropriate typedef to inherit from std::unary_function if that is the case. A similar method works for the std::binary_function type, which also has similar requirements as the unary function type.
posted @ Friday, February 09, 2007 2:45 PM