Howard E. Hinnant
2006-12-18

Motivation for the rvalue From

Consider this code:

template <class T>
class container
{
public:
    ...
    template <class U>
    typename where
    <
        is_convertible<U, T>::value,
        iterator
    >::type
    insert(const_iterator position, U&& x);
};

The above is not contrived code. I wrote exactly this code when putting move semantics into std::map. In that case T is a pair<const key_type, mapped_type>, and U is some type that must be convertible to the pair, most notably a pair<key_type, mapped_type> (without the const). The rationale for accepting such (non-const) types is so that values can be efficiently moved into the map.

If an lvalue argument is passed (say of type P), U gets deduced as a P&, else it gets deduced as P. Subsequently is_convertible will be testing P& against T if an lvalue argument was passed, or P against T if an rvalue was passed --- exactly as needed if is_convertible considers its From types to be rvalues!

If instead is_convertible traffics in lvalue From types, then the above code is wrong because it will consider an rvalue P as an lvalue when doing the is_convertible test. To regain the correct semantics, the following rewrite will work:

template <class T>
class container
{
public:
    ...
    template <class U>
    typename where
    <
        is_convertible<U&&, T>::value,
        iterator
    >::type
    insert(const_iterator position, U&& x);
};

Now if an lavlue P is passed, U is deduced as P& and is_convertible will compare a P& && which collapses to P& to T. And if an ravlue P is passed, U is deduced as P and is_convertible will compare a P&& to T.

So either way can be made to work, but the former seems easier to read, easier to understand, and less error prone (things just work with the most natural syntax). That is, you can write the latter and have your code work correctly no matter what is_convertible does with From types (rvalue or lvalue). However if you forget to add the "&&", or if you get confused and can't remember whether U& or U&& is correct, just writing U will give you the correct code only if is_convertible considers From as an rvalue.

Reference Implementation

This implementation handles everything but access and ambiguity checks.

//  (C) Copyright Howard Hinnant 2005.
//  Use, modification and distribution are subject to the Boost Software License,
//  Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt).
//
//  See http://www.boost.org/libs/type_traits for most recent version including documentation.

// template <class From, class To> struct is_convertible : public integral_constant<bool, b> {};

// If the following test function is well formed code b is true, else it is false.
// template <class T> typename add_rvalue_reference<T>::type create();
// To test() {return create<From>();}

struct two {char _[2];};

namespace is_convertible_imp
{
template <class T> char  test(T);
template <class T> two test(...);
template <class T> T source();

// is_array_function_or_void<T>::value == 1 : T is an array
// is_array_function_or_void<T>::value == 2 : T is a function
// is_array_function_or_void<T>::value == 3 : T is a void
// is_array_function_or_void<T>::value == 0 : T is not an array, function or void
template <class T,   bool IsArray    = is_array<T>::value,
                     bool IsFunction = is_function<T>::value,
                     bool IsVoid     = is_void<T>::value>
                     struct is_array_function_or_void                      {enum {value = 0};};
template <class T> struct is_array_function_or_void<T, true, false, false> {enum {value = 1};};
template <class T> struct is_array_function_or_void<T, false, true, false> {enum {value = 2};};
template <class T> struct is_array_function_or_void<T, false, false, true> {enum {value = 3};};
}

// is_convertible_check<T>::_v If T is a an object (other than array) or reference to an object other
// than an array, make sure it is a complete type
template <class T,
    unsigned = is_convertible_imp::is_array_function_or_void<typename remove_reference<T>::type>::value>
struct is_convertible_check
{
    static const size_t _v = 0;
};

template <class T>
struct is_convertible_check<T, 0>
{
    static const size_t _v = sizeof(T);
};

// There are sixteen cases to handle:  From in [0 ... 3] crossed with To in [0 ... 3]
// Primary template handles <T1, T2, 0, 0> cases (both types are non-array objects,
//    or references.  source<T1>() is used to form an rvalue T1 to test
//    against T2.
template <class T1, class T2,
    unsigned T1_is_array_function_or_void = is_convertible_imp::is_array_function_or_void<T1>::value,
    unsigned T2_is_array_function_or_void = is_convertible_imp::is_array_function_or_void<T2>::value>
struct _is_convertible
    : public integral_constant<bool,
        sizeof(is_convertible_imp::test<T2>(is_convertible_imp::source<T1>())) == 1>
{};

// Handle To == not an array, function or void (0)

    // Except for pointer types, arrays are not convertible to anything except a const lvalue
    //    reference to themself, or cv rvalue reference to themself
    template <class T1, class T2> struct _is_convertible<T1, T2, 1, 0> : public false_type {};
    
    // Answer true to convertible to const lvalue reference to self
    template <class T1> struct _is_convertible<T1, const T1&, 1, 0> : public true_type {};
#if RVALUE_REF
    // Answer true to convertible to cv rvalue reference to self
    template <class T1> struct _is_convertible<T1, T1&&, 1, 0> : public true_type {};
    template <class T1> struct _is_convertible<T1, const T1&&, 1, 0> : public true_type {};
    template <class T1> struct _is_convertible<T1, volatile T1&&, 1, 0> : public true_type {};
    template <class T1> struct _is_convertible<T1, const volatile T1&&, 1, 0> : public true_type {};
#endif

    // For is_convertible<array-type, pointer-type>, transform into
    //    is_convertible<pointer-type, pointer-type> and retest
    template <class T1, class T2> struct _is_convertible<T1, T2*, 1, 0>
        : public integral_constant<bool, _is_convertible<typename remove_all_extents<T1>::type*, T2*>::value> {};
    
    // Except for pointer types, functions are not convertible to anything except an
    //   rvalue reference to self
    template <class T1, class T2> struct _is_convertible<T1, T2, 2, 0> : public false_type {};
#if RVALUE_REF
    template <class T1> struct _is_convertible<T1, T1&&, 2, 0>         : public true_type {};
#endif

    // For is_convertible<function-type, function-type*cv>, return true
    template <class T1>            struct _is_convertible<T1, T1*, 2, 0>               : public true_type {};
    template <class T1>            struct _is_convertible<T1, T1*const, 2, 0>          : public true_type {};
    template <class T1>            struct _is_convertible<T1, T1*volatile, 2, 0>       : public true_type {};
    template <class T1>            struct _is_convertible<T1, T1*const volatile, 2, 0> : public true_type {};
    
    // void types are not convertible to anything but other void types (void->void handled elsewhere)
    template <class T1, class T2> struct _is_convertible<T1, T2, 3, 0> : public false_type {};

// Handle To == array (1)

    // Nothing is convertible to an array type, not even the same array type
    template <class T1, class T2> struct _is_convertible<T1, T2, 0, 1> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 1, 1> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 2, 1> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 3, 1> : public false_type {};

// Handle To == function (2)

    // Nothing is convertible to a function type, not even the same function type
    template <class T1, class T2> struct _is_convertible<T1, T2, 0, 2> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 1, 2> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 2, 2> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 3, 2> : public false_type {};

// Handle To == void (3)

    // Nothing is convertible to a void type, except for a void type
    template <class T1, class T2> struct _is_convertible<T1, T2, 0, 3> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 1, 3> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 2, 3> : public false_type {};
    template <class T1, class T2> struct _is_convertible<T1, T2, 3, 3> : public true_type {};

// Primary trait forms completness tests
template <class T1, class T2> struct is_convertible : public _is_convertible<T1, T2>
{
    static const size_t _complete_check1 = is_convertible_check<T1>::_v;
    static const size_t _complete_check2 = is_convertible_check<T2>::_v;
};