Jack Deeth
Jack Deeth

Reputation: 3357

Avoiding duplication when defining specialised template class constructors

Suppose I have a template class:

template<typename T>
class Widget {
public:
    Widget(const std::string& name, int i) : t_(name),  cf_(name), ci_(i) {}
private:
    T t_;
    const Foo cf_;
    const int ci_;
}

And suppose, in this context, T will only be Foo or int. Anything other than Widget<Foo> and Widget<int> is nonsense in this context.

The constructor declared above works fine for Widget<Foo>. Is there a way I can define a specialised constructor for Widget<int>, which assigns to t_ differently, but without copy-pasting the initialisation of cf_ and ci_?

Something similar in spirit to the call to Base(a) in Derived::Derived(int a, int b) : Base(a), b_(b) {} perhaps?

Upvotes: 1

Views: 59

Answers (2)

Sam Varshavchik
Sam Varshavchik

Reputation: 118445

Specializing the entire Widget<int> class is one option. Short of that, there are various approaches that can be used to avoid specializing the entire class; however they depend heavily on exactly how you need to construct it.

For this example, let's say that for Widget<Foo> you want to construct the class member t_, which will be a Widget using the std::string name parameter (Widget has a constructor that takes a std::string parameter), and for Widget<int> you want to construct the class member t_ which will now be an int using the int i parameter.

First, define a specialized helper template function that picks the right parameter:

template<typename T>
auto t_param(const std::string &name, int i);

template<>
auto t_param<Foo>(const std::string &name, int i)
{
    return name;
}

template<>
auto t_param<int>(const std::string &name, int i)
{
    return i;
}

With this in place, your constructor becomes simply:

Widget(const std::string& name, int i)
        : t_(t_param<T>(name, i)),  cf_(name), ci_(i) {}

Upvotes: 1

Jarod42
Jarod42

Reputation: 217810

Tag dispatching, and forwarding constructor may help

template <typename> struct Tag {};


template<typename T>
class Widget {
public:
    Widget(const std::string& name, int i) : Widget(Tag<T>{}, name, i) {}

private:
    Widget(Tag<int>, const std::string& name, int i) :
        Widget(Tag<void>{}, 42, name, i)
    {}

    Widget(Tag<Foo>, const std::string& name, int i) :
        Widget(Tag<void>{}, name, name, i)
    {}

    template <typename U>
    Widget(Tag<void>, U&& u, const std::string& name, int i) :
         t_(std::forward<U>(u)),
         cf_(name),
         ci_(i)
    {}

private:
    T t_;
    const Foo cf_;
    const int ci_;
};

Upvotes: 5

Related Questions