1

I am trying to do something like this:

struct Foo {
    int _val;
    Foo(int v) : _val(v){} 
};

struct Bar {
    const std::string &_name;
    Bar(const std::string &name) : _name(name) {} 
};

template<typename T>
struct Universal {
    T _t;
    Universal(...) : _t(...) {} 
};

// I want to use Universal for Foo abd Bar in the same way:
Universal<Foo> UF(9);       // 9 is for Foo
Universal<Bar> UB("hello"); // "hello" is for bar

In the code above, I would like to forward all parameters in Universal's constructor to T's constructor.

How could I do it?

blackball
  • 718
  • 1
  • 6
  • 19

2 Answers2

4

You need to make Universal constructor be a variadic template, and use a parameter pack and perfect forwarding.

template<typename T>
struct Universal {
    T _t;

    template <typename... Args>
    Universal(Args&&... args) : _t(std::forward<Args>(args)...) {} 
};

Unfortunately as AndyG notes in the comments, this means that if you try and copy a non-const Universal object, the forwarding version gets preferred - so you need explicit const and non-const copy constructors!

template<typename T>
struct Universal {
    T _t;

    template <typename... Args>
    Universal(Args&&... args) : _t(std::forward<Args>(args)...) {} 

    Universal(const Universal& rhs): _t(rhs._t) {}
    Universal(      Universal& rhs): _r(rhs._t) {}

    // ... but not move constructors.
};

or use the SFINAE approach shown in this answer, to make sure the default constructor is preferred.

Community
  • 1
  • 1
0

Simple:

template<typename T>
struct Universal {
    T _t;
    Universal(const T& val) : _t(val) {} 
};

iff performance is of concern or if you want to support move only types you can forward the parameter:

template<typename T>
struct Universal {
    T _t;
    template <class U>
    Universal(U&& val) : _t(std::forward<U>(val)) {} 
};

Please note we need a template parameter U for the ctor in order for the forwarding to work. T&& would be just a rvalue reference.

If you want to get more fancy, you can construct in place:

template<typename T>
struct Universal {
    T _t;
    template <class... Args>
    Universal(Args&&... args) : _t(std::forward<Args>(args)...) {} 
};

As AndyG suggested in a comment, extra work would need to be done if you want cpy and mv ctors to work as expected.

The rule is: keep it simple unless there is a good reason justifying the complications. I think that the first variant is more than enough for you.

Community
  • 1
  • 1
bolov
  • 72,283
  • 15
  • 145
  • 224
  • If Foo or Bar have an explicit constructor, or don't have a copy constructor, then you will have to forward the parameter, whether or not you care about performance. – Martin Bonner supports Monica Oct 11 '16 at 12:59
  • the explicit ctor is not a problem. You are right about move-only types. Ty, good catch – bolov Oct 11 '16 at 13:01
  • I think the explicit ctor is a problem: `struct Foo{ explicit Foo(int i) {} }; void f(const Foo& f) {}; f(1);` will fail because you are trying to implicitly create a Foo. That is what you are suggesting in your first suggestion. – Martin Bonner supports Monica Oct 11 '16 at 13:04
  • @MartinBonner if the constructor is explicit, then the expected way would be `f(Foo{1})`. That's why it's explicit. – bolov Oct 11 '16 at 13:09
  • similar, I would want `universal` to work like this: `f(universal){}; f(Foo{1})`, which it does. – bolov Oct 11 '16 at 13:10