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.

+3


source to share


1 answer


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 .

+4


source







All Articles