質問

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.

役に立ちましたか?

解決

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
                  >{}, "!");
}

他のヒント

I would suggest writing the specialization without the cv- and ref- qualifiers, and using a wrapper around std::common_type<>, like this:

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

/* Our wrapper which passes decayed types to std::common_type<>. */
template <typename... T>
using CommonType = std::common_type<decay_t<T>...>;

namespace std {

  /* Specialization for my_type<>. */
  template <intmax_t lhs_min, intmax_t lhs_max,
            intmax_t rhs_min, intmax_t rhs_max>
  struct common_type<my_type<lhs_min, lhs_max>,
                     my_type<rhs_min, rhs_max>> {
    using type = /* ... */;
  };

}  // std

There are already specializations for std::chrono::duration and std::chrono::time_point which only specialize without cv- and ref- qualifiers.

However, those specializations only get used when cv- and ref- qualifiers are not specified. It's not obvious that they aren't being used because,

static_assert(
  std::is_same<std::common_type<const std::chrono::milliseconds,
                                std::chrono::microseconds &&>::type,
               std::chrono::microseconds>::value, "");

works just fine.

I was confused as to how the specialization was being used until I realized that it wasn't. The generic implementation of std::common_type<> uses decltype() on an if-else expression, like this:

template <typename Lhs, typename Rhs>
using common_type_impl_t = 
    decay_t<decltype(true ? std::declval<Lhs>() : std::declval<Rhs>())>;

Note: It uses SFINAE to pick this one if it's successful otherwise leaves type undefined.

Now we can test that this actually works for std::chrono::duration by testing it out,

static_assert(
  std::is_same<common_type_impl_t<const std::chrono::milliseconds,
                                  std::chrono::microseconds &&>,
               std::chrono::microseconds>::value, "");

which passes. Now, throw volatile in there and it'll break just as @Vincent pointed out, which further proves that the specializations are not being used.

So my conclusion is that one of the two will happen:

  • The standard implementation will change such that the specializations will get used even if there are cv- and ref- qualifiers present, in which case you can toss out the CommonType<> wrapper which is trivial anyway, and you've only defined one specialization.
  • The standard implementation does't change, but you've still only defined one specialization and you use CommonType<> instead.
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top