qeadz
qeadz

Reputation: 1516

Acceptable way to restrict variadic templates in constructors

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

Answers (1)

T.C.
T.C.

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

Related Questions