Reputation: 1516
To explain my question, I'll first paste some example code then ask the related question.
template< typename... CONDITIONS >
struct all_true;
template<>
struct all_true<>
{
const static bool value = true;
};
template< typename CONDITION, typename... CONDITIONS >
struct all_true< CONDITION, CONDITIONS... >
{
const static bool value = CONDITION::value && all_true<CONDITIONS...>::value;
};
template< class T >
class myobject
{
struct placeholder {};
template< typename... Ts >
struct CheckVaradicArgs
{
typedef
typename std::enable_if<
all_true< std::is_convertible<Ts, T>... >::value
, placeholder >::type type;
};
template< typename... Ts >
myobject( placeholder check, Ts... params )
{}
public:
myobject( const myobject& other )
{
std::cout << "Copy constructor" << std::endl;
}
template< typename... Ts >
myobject( Ts... params )
: myobject( typename CheckVaradicArgs<Ts...>::type(), params... )
{
std::cout << "Ts constructor with " << sizeof...(params) << std::endl;
}
};
In the code above I have a templated type myobject. The desire is that it would have a constructor which accepts any number of types which are convertible to the type myobject is templated on.
Additionally I'd like to have the usual copy-constructor as well as other constructors which might take special types that can work with myobject.
The problem I encountered was how to write the constructor which used the variadic template. Clearly it should not accept any of the types for which I have other constructors . However it should accept types which pass a specific test (in this example, the is_convertible test).
The way I have solved this, which works with GCC, is the above solution - the variadic constructor passes the parameters off to a secondary private constructor which also takes a dummy parameter. The dummy parameter is there to allow SFINAE to disregard the constructor if the other arguments don't satisfy some conditional.
So my question(s) are:
1) Is this an acceptable solution? Or am I just lucky that GCC is treating the private constructor I've delegated to as part of the overload resolution for the public constructor?
2) Is there a cleaner way to do this?
I have searched and found answers to similar kinds of problems, but the ones I have found are implemented on functions not constructors. They use enable_if on the return type to force the compiler to exclude the function from the overload resolution table.
Upvotes: 3
Views: 722
Reputation: 137310
Overload resolution doesn't consider accessibility; that check is done later.
What you are doing is not actually SFINAE; it is not affecting overload resolution, only making the instantiation of the template constructor ill-formed after it's selected by overload resolution.
We can see this by adding a non-template constructor that's not as good a match:
template< class T >
class myobject
{
/* ... */
public:
/* ... */
myobject( const char * ptr )
{
std::cout << "const char * constructor" << std::endl;
}
template< typename... Ts >
myobject( Ts... params )
: myobject( typename CheckVaradicArgs<Ts...>::type(), params... )
{
std::cout << "Ts constructor with " << sizeof...(params) << std::endl;
}
};
char * p = nullptr;
myobject<int> something(p);
If it were SFINAE, the template would be taken out of overload resolution, and the const char *
constructor would be selected. Instead, you get a compile error as the compiler tries to instantiate the variadic template (g++ does the same thing).
To SFINAE, just add an extra template parameter:
template< class T >
class myobject
{
template< typename... Ts >
using CheckVariadicArgs =
typename std::enable_if<
all_true< std::is_convertible<Ts, T>... >::value, int
>::type;
public:
myobject( const myobject& other )
{
std::cout << "Copy constructor" << std::endl;
}
template< typename... Ts, CheckVariadicArgs<Ts...>* = nullptr >
myobject( Ts... params )
{
std::cout << "Ts constructor with " << sizeof...(params) << std::endl;
}
};
Demo.
Upvotes: 4