3

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.

Constructor
  • 7,273
  • 2
  • 24
  • 66
qeadz
  • 1,476
  • 1
  • 9
  • 17
  • I'm not sure I understand the problem; if the variadic template constructor and a non-template constructor are equally good matches, the non-template will be picked by overload resolution. – T.C. Aug 12 '14 at 21:59

1 Answers1

4

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Thank you very much for the clarification. The template alias is nicer than the struct I had too. – qeadz Aug 12 '14 at 23:55
  • 2
    +1 Nice! One new thing I learnt from this answer is that in a variadic function template, the variadic types don't always have to be declared last when they are inferable from the function's arguments (obvious ambiguities aside). – Ose May 28 '15 at 13:44
  • The variadic ctor must accept rvalue refs to be greedy and override all other ctors. Right now if you remove the defaulted template param it still works as expected - copy ctor is called! – user362515 Feb 03 '16 at 20:30
  • @user362515 `Ts...` is slightly less greedy than `Ts&&...` in that it won't hijack the copy/move ctors, but otherwise it's still an exact match for everything under the sun. – T.C. Feb 03 '16 at 21:44