Этого можно добиться, ограничив шаблонный конструктор (который будет использоваться при преобразовании) с помощью std::enable_if
и некоторого метапрограммирования шаблона:
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
будет проходить вверх по шаблонам From
параметр шаблона, пока он не станет равным To
(в этом случае он наследует от std::true_type
, т.е. may_convert<...>::value
равен true
), или пока не закончатся шаблоны (в этом случае may_convert<...>::value
будет false
).
Теперь все, что остается, - это соответственно ограничить ваш конструктор:
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&) {}
};
Таким образом, конструктор существует, только если may_convert<...>::value
равен true
.В противном случае преобразование завершится неудачей.
Примеры
Вот пример того, как may_convert
работает в вашем примере (преобразование из D = A<A<A<int>>>
в B = A<int>
):
Конструктор существует, только если may_convert<A, D, B>::value
равно true
may_convert<A, D, B>
соответствует последней специализации (потому что D = A<C>
и B = A<int>
параметры выводятся как T = C
и U = int
) и наследуются от may_convert<A, C, B>
may_convert<A, C, B>
, снова совпадающих с последней специализацией (T = B
, U = int
) инаследуется от may_convert<A, B, B>
На этот раз оба типа совпадают, поэтому первая специализация совпадает, и все наследуется от std::true_type
, что позволяет конструктору.
С другой стороны, представьте using E = A<double>
, который не должен преобразовываться в B
:
Конструктор будет включен, только если may_convert<A, E, B>::value
true
may_convert<A, E, B>
соответствует последней специализации и наследуется от may_convert<A, double, B>
, поскольку double
не является A<...>
, ни одна из специализаций не совпадает, поэтому мы возвращаемся к регистру по умолчанию, который наследуется от std::false_type
.
Следовательно, may_convert<A, E, B>::value
равно false
, и преобразованиене удается.