Question
I have a series of ~10 template classes A, B, C, D, ...
I want to enable conversions from a class to previous classes in the series
You can achieve this by constraining a templated constructor (which will be used in conversion) using std::enable_if
and some template metaprogramming:
template <template <class> typename BaseTemplate,
typename From,
typename To,
typename Enable = void>
struct may_convert
: public std::false_type {};
template <template <class> typename BaseTemplate,
typename T>
struct may_convert<BaseTemplate, BaseTemplate<T>, BaseTemplate<T>, void>
: public std::true_type {};
template <template <class> typename BaseTemplate,
typename T,
typename U>
struct may_convert<BaseTemplate, BaseTemplate<T>, BaseTemplate<U>,
typename std::enable_if<!std::is_same<T, U>::value>::type>
: public may_convert<BaseTemplate, T, BaseTemplate<U>> {};
may_convert
will walk up the templates of the From
template parameter until it is equal to To
(in which case it inherits from std::true_type
, i.e. may_convert<...>::value
is true
), or until the templates run out (in which case may_convert<...>::value
is false
).
Now, all that remains is constraining your constructor appropriately:
template <typename Convertible>
class A {
public:
A() {}
template <typename T,
typename = typename std::enable_if<
may_convert<A, T, A<Convertible>>::value>::type>
A(const T&) {}
};
This way, the constructor exists only if may_convert<...>::value
is true
. Otherwise, the conversion will fail.
Here is an example of how may_convert
works in your example (converting from D = A<A<A<int>>>
to B = A<int>
):
The constructor only exists if may_convert<A, D, B>::value
is true
may_convert<A, D, B>
matches the last specialization (because D = A<C>
and B = A<int>
, the parameters are deduced as T = C
and U = int
) and inherits from may_convert<A, C, B>
may_convert<A, C, B>
again matches the last specialization (T = B
, U = int
) and inherits from may_convert<A, B, B>
This time, the two types are equal, so the first specialization matches, and the entire thing inherits from std::true_type
, enabling the constructor.
On the other hand, imagine a using E = A<double>
that should not convert to B
:
The constructor will only be enabled if may_convert<A, E, B>::value
is true
may_convert<A, E, B>
matches the last specialization, and inherits from may_convert<A, double, B>
Because double
is not an A<...>
, none of the specializations match, so we fall back to the default case, which inherits from std::false_type
.
Therefore, may_convert<A, E, B>::value
is false
, and the conversion fails.
Although @hlt's approach will do what you ask, without knowing more about the context, I'm wary about implementing the conversion in A
. In the cases I can think of, A
shouldn't be aware of B
, C
or D
, so I'll suggest a different implementation.
You can create a variant of your test 3, where you implement one conversion operator per class, but where you also inherit a templated indirect conversion operator, like so:
#include <type_traits>
template <typename T1, typename T2>
struct indirect_conversion {
template <typename T, typename = std::enable_if_t<std::is_constructible_v<T, T2>>>
operator T() {
return static_cast<T1 *>(this)->operator T2();
}
};
struct A {};
struct B : indirect_conversion<B, A> {
operator A();
};
struct C : indirect_conversion<C, B> {
operator B();
};
struct D : indirect_conversion<D, C> {
operator C();
};
A a = D();