A valid way to restrict variational patterns in constructors
To explain my question, I'll first paste in the example code and 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 above code, I have the temobated templated type. The desire is that it will have a constructor that accepts any number of types that can be converted to the type myobject it is templated on.
In addition, I would like to have a regular copy constructor as well as other constructors that can take special types that can work with myobject.
The problem I ran into was how to write a constructor that used the variational pattern. Clearly, it shouldn't accept any of the types for which I have other constructors. However, it must accept types that pass the particular test (in this example is_convertible test).
The way I solved it, which works with GCC, is the above solution. The Variadic constructor passes parameters to a secondary private constructor that also accepts a dummy parameter. The dummy parameter allows SFINAE to ignore the constructor if the other arguments do not satisfy some condition.
So my questions are:
1) Is this an acceptable solution? Or am I just lucky that GCC handles the private constructor I delegated as part of the overload resolution for the public constructor?
2) Is there a cleaner way to do this?
I searched and found answers to similar problems, but the ones I found are implemented in functions, not constructors. They use enable_if on the return type to force the compiler to exclude this function from the overload permission table.
source to share
Overload resolution does not consider availability; that the check is done later.
What you are doing is not really SFINAE; it does not affect overload resolution, thereby creating an instantiated template constructor that is ill-formed after being selected using overload resolution.
We see this by adding a no-template constructor, which is not that great:
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 thrown out of overload resolution and the constructor would be selected const char *
. Instead, you get a compilation error when the compiler tries to instantiate the variational template (g ++ does the same ).
In SFINAE, just add an additional 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 .
source to share