Reputation: 28883
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
.
Upvotes: 9
Views: 1130
Reputation: 39141
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
>{}, "!");
}
Upvotes: 2
Reputation: 7904
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:
CommonType<>
wrapper which is trivial anyway, and you've only defined one specialization.CommonType<>
instead.Upvotes: 2