class template specialization that accepts all versions of const / volatile qualifications and & vs &&

c++, c++11, template-meta-programming, template-specialization, templates

Solution

As far as I know, you don't need to completely specialize both sides of the binary `common_type`. This allows reducing the amount of specializations to 12 for one side. If you only need a common type between specializations of `my_type` and `my_type`, than it's sufficient to specialize on one side. Otherwise, you'd had to clone them on the right side, yielding 24 specializations.

struct my_type;
struct unique_t;

#include <type_traits>

template<class L, class R, class = void>
struct mytype_common_type
{
    // not many specializations are required here,
    // as you can use std::decay and don't have to use "Exact Matches"
    using type = unique_t;
};

namespace std
{
    template<class T> struct common_type<my_type, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type const, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type volatile, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type const volatile, T>
    : mytype_common_type<my_type, T> {};

    template<class T> struct common_type<my_type&, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type const&, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type volatile&, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type const volatile&, T>
    : mytype_common_type<my_type, T> {};

    template<class T> struct common_type<my_type&&, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type const&&, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type volatile&&, T>
    : mytype_common_type<my_type, T> {};
    template<class T> struct common_type<my_type const volatile&&, T>
    : mytype_common_type<my_type, T> {};
}

template<class T>
using Decay = typename std::decay<T>::type;

int main()
{
    static_assert(std::is_same<unique_t,
                    std::common_type<my_type const volatile&&, int>::type
                  >{}, "!");
}

Problem

I am specializing `std::common_type` for my type. I defined the following specialization: ``` common_type<my_type, my_type> ``` And all is well. Then someone comes along and calls `std::common_type<my_type, my_type &>`. The default version acts the same if you pass a reference vs. not a reference (as it calls `std::decay` on the types). However, it doesn't defer to the non-reference version of `std::common_type`, which I would need to work correctly. Is there a better way than having to do something like this (leaving out rvalue-reference to const for simplicity): ``` common_type<my_type, my_type> common_type<my_type, my_type &> common_type<my_type, my_type const &> common_type<my_type, my_type volatile &> common_type<my_type, my_type const volatile &> common_type<my_type, my_type &&> common_type<my_type, my_type volatile &&> common_type<my_type &, my_type> common_type<my_type const &, my_type> common_type<my_type volatile &, my_type> common_type<my_type const volatile &, my_type> common_type<my_type &&, my_type> common_type<my_type volatile &&, my_type> common_type<my_type &, my_type &> common_type<my_type &, my_type const &> common_type<my_type &, my_type volatile &> ... ``` Surely there is a better way? By my count, that is 49 possible versions if we ignore `const &&` and `const volatile &&` Note: my_type is actually a class template itself, so the specialize actually looks more like ``` template<intmax_t lhs_min, intmax_t lhs_max, intmax_t rhs_min, intmax_t rhs_max> class common_type<my_type<lhs_min, lhs_max>, my_type<rhs_min, rhs_max>> ``` Where the result is `my_type<min(lhs_min, rhs_min), max(lhs_max, rhs_max)>` The solution would be fairly straightforward if I had full control over the primary template definitions, but I obviously cannot change `std::common_type`.

Original source