Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use std::enable_if to conditionally select a variadic constructor?

I am trying to make a class which should inherit the constructors from other classes but without inheriting from those classes itself.

At one point during the initialization of my class, I want to use perfect forwarding to create an object of the type whose constructor matched the given arguments.

Except for the default constructor with no arguments, there shall be no ambiguities.

This is my code:

#include <string>

using namespace std;

//NOTE: this class is just an example to demonstrate the problem
class String {
    public:
        //default constructor to prevent ambiguity
        String() {}

        //construct from wstring
        template<typename... Args>
        String(enable_if<is_constructible<wstring, Args...>::value, Args>::type&&... args) : ws(forward<Args>(args)...) {}

        //construct from string
        template<typename... Args>
        String(enable_if<is_constructible<string, Args...>::value, Args>::type&&... args) : s(forward<Args>(args)...) {}
    private:
        string s;
        wstring ws;
};

void foo(const String& string) {
}

int main()
{
    foo(L"123");
    foo("123");
    return 0;
}

I tried many things but I just can't get it to work.

  • In the current approach enable_if fails to automatically deduct the template args (I think)
  • Since I use the constructor I can't use enable_if on the return value
  • Adding another default parameter for enable_if won't work because the constructor is variadic
  • When I remove enable_if from the function arguments the compiler complains about invalid overloads (of course)

Is there an elegant way to solve this problem?

Edit: The one implicit conversion that is allowed by the standard should not occur in my class. [example code edited]

One solution that works with the example above would be to define a single variadic constructor and perfect forward the arguments to a conditioned initialization function. However, I would like to avoid this overhead because members need to be default constructed and this may not work in other cases.

(Feel free to edit the question if things can be made clearer)

like image 209
user1492625 Avatar asked Oct 20 '13 12:10

user1492625


1 Answers

I can't understand the problem and also the solution. If I want to use 2 different types for a method or a constructor I can simply write both types. There is no need to have a template with SFINAE for that reason! This can simply done by specialization.

class A 
{
    public:
    template <typename ... Args>
    A(const wstring &, Args ... );

    template <typename ... Args>
    A(const string &, Args ...);
};

To have a template which exact match for only one type is not really semantically a template :-)

After reading your comment I got this solution:

class foo
{
    public:
        template <typename ... Args>
        foo( const string &&x, Args ... ) { cout << "Using string" << endl; }

        template <typename ... Args>
        foo( const wstring &&x, Args ...) { cout << "Using wstring" << endl; }
};

int main()
{
    foo("123");
    foo(L"123");

    foo("123", 1);
    foo(L"123", 1.11);
    return 0;
}

and this return as expected:

Using string
Using wstring
Using string
Using wstring
like image 54
Klaus Avatar answered Sep 17 '22 16:09

Klaus