Thomas Monkman
Thomas Monkman

Reputation: 415

Constructing class from variadic templates

I'm trying to create a class called Sink which will create a pointer to the class you pass in, this is to wrap an api in RAII.

In the full version of this code, the custom class also inherits from another class, and there is static assets to check this. The pointer is also passed to the api.

But to keep it simple I've removed this.

This is the error I get from cpp.sh

 In function 'int main()':
43:30: error: no matching function for call to 'Sink<OneArg>::Sink(int)'
43:30: note: candidate is:
10:5: note: Sink<CustomSink, Args>::Sink(Args&& ...) [with CustomSink = OneArg; Args = {}]
10:5: note:   candidate expects 0 arguments, 1 provided

Code:

#include <string>
#include <iostream>
#include <memory>
#include <utility>

template<typename CustomSink, typename... Args>
class Sink
{
public:
    Sink(Args&&... args)
    {        
       _ptr = std::make_unique<CustomSink>(std::forward<Args>(args)...);        
    }
    ~Sink() 
    {
    }
private:
    std::unique_ptr<CustomSink> _ptr;
};


//////////////////////////////////////////////////////////////////////
class NoArg
{
public:
    NoArg() {};
    ~NoArg() {};
};

class OneArg
{
public:
    OneArg(int a) {
        std::cout << a << '\n';
    };
    ~OneArg() {};
};
//////////////////////////////////////////////////////////////////////


int main(){
    Sink<NoArg> noArgSink;   
    Sink<OneArg> oneArgSink(5);  

    return 0;
}

Upvotes: 4

Views: 283

Answers (4)

Garf365
Garf365

Reputation: 3707

According to your design, you have to write:

Sink<OneArg, int> oneArgSink(5);

demo

Indeed, number and type of arguments of constructor are determined by variadic part of your template (typename ... Args). But this part is attached to class, not to constructor. So you have to specify it when you instantiate your template.

Otherwise, if you want to respect your instantiation and let compiler determines template arguments, you have to move variadic part of your template to constructor (see Jarod42 answer, or NathanOliver's one)

Upvotes: 3

pepo
pepo

Reputation: 699

The way you declared the class Sink means you need to specify Args... at template instantiation.

That means if you declare Sink<OneArg>, you are effectively making Args... empty and the constructor doesn't expect any arguments. That is why the compiler complains about passing an argument to constructor.

You only need the variadic template in the constructor. This way you can even accomodate classes having multiple constructors with different number of arguments.

template<typename CustomSink>
class Sink
{
public:
    template<typename... Args>
    Sink(Args&&... args)
    {
        _ptr = std::make_unique<CustomSink>(std::forward<Args>(args)...);
    }
    ~Sink()
    {
    }
private:
    std::unique_ptr<CustomSink> _ptr;
};

Upvotes: 1

NathanOliver
NathanOliver

Reputation: 180595

Your issue here is with the placement of the template type Args. Right now you have Args in the class tempalte so

Sink<OneArg> oneArgSink(5);

Says Args is empty so Sinks constructor expects no arguments. What you need to do is move Args to the constructor by making it a template. That gives you

template<typename... Args>
Sink(Args&&... args)
{        
   _ptr = std::make_unique<CustomSink>(std::forward<Args>(args)...);        
}

And now the constructor will deduce the arguments passed to it instead of having to specify it when you declare the class.

Upvotes: 5

Jarod42
Jarod42

Reputation: 217275

The template arguments of the constructor should be moved from the class to the templated constructor:

template<typename CustomSink>
class Sink
{
public:
    template <typename... Args>
    Sink(Args&&... args)
    {        
       _ptr = std::make_unique<CustomSink>(std::forward<Args>(args)...);        
    }
private:
    std::unique_ptr<CustomSink> _ptr;
};

Upvotes: 5

Related Questions