Create two tiers of constructor. Then tag dispatch.
template<template<class...>class Z, class T>
struct is_template:std::false_type{};
template<template<class...>class Z, class...Ts>
struct is_template<Z, Z<Ts...>>:std::true_type{};
struct foo {
private:
template<class T> struct tag{ explicit tag(int) {} };
public:
foo( tag<std::true_type>, const char*, size_t );
template<class...Ts>
foo( tag<std::false_type>, Ts&&...ts );
public:
foo() = default; // or whatever
template<class T0, class...Ts,
std::enable_if_t<!is_template<tag, std::decay_t<T0>>{},int> =0>
foo(T0&&t0, Ts&&...ts):
foo( tag<typename std::is_constructible< foo, tag<std::true_type>, T0&&, Ts&&... >::type>{0}, std::forward<T0>(t0), std::forward<Ts>(ts)... )
{}
};
The "preferred" ctors are prefixed with std::true_type
, the "less preferred" ctors are prefixed with std::false_type
.
This has the usual imperfections of perfect forwarding. If you take initializer lists, you'll want to have another "public" ctor that takes that explicitly, for example. And function name argument magical overloading won't work. NULL
is an int
. Etc.
You can imagine a version that, instead of having two tiers, has an arbitrary number. The is_constructible< ... >
clause in the public facing ctor instead is replaced with some magic that finds the highest N such that tag<N>, blah...
can construct the object (or, lowest N
, whichever way you want to do it). Then it returns the type tag<N>
, which then dispatches to that tier.
Using a technique like this:
template <typename... T,
typename = std::enable_if_t<!std::is_constructible<C, T&&...>::value>
>
C(T&&... ) { }
runs into a serious problem down the road, as we have instantiated is_constructible
in a context where it gets the answer wrong. And in practice, compilers cache the results of template instantiations, so now the result of is_constructible
is compiler order dependent (ODR violation I suspect).