Code like (c++14):
struct S { int a; int b; };
class C
{
public:
C(char const*, size_t) {} // 1
C(S const&) {} // 2
C(S const*) {} // 3
template<typename ...T> C(T&& ...) {} // 4
// C(S) {} // 5
// C(S*) {} // 6
};
S s { 1, 2 };
C c1 { s }; // calls 4 and not 2
C c2 { "abc", 3 }; // calls 4 and not 1
C c3 { (char const*)"abc", (size_t)3 }; // calls 1 - ok
C c4 { s }; // calls 5 if uncommented
C c5 { &s }; // calls 6 if uncommented
S const s2 {};
C c6 { &s2 }; // calls 3
Simple constructor is called if it has exact the same signature as the passed parameter. Is there some trick to use common constructors as usual with a variadic template constructor, without copying classes, passed as parameters, and overloading constructors like:
C(S const*) {}
C(S*) {}
And without additional tags in constructors
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).
You can enable your variadic constructor if and only if those arguments do not allow you to construct C
in some other way using std::is_constructible
.
That is:
template <typename... T,
typename = std::enable_if_t<!std::is_constructible<C, T&&...>::value>
>
C(T&&... ) { }
With that change, C c1{s}
does call (2)
and C c2{"abc", 3}
does call (1)
, whereas C c7{1, 2, 3, 4, 5}
will call (4)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With