Can you perfectly forward without templates

Viewed 122

I have a class that has multiple constructors:

class Obj
{
    public:
        Obj(int x, int y)           {}
        Obj(std::string const& str) {}
        Obj(char v, int s)          {}
};

Now I want to add an Options object to the constructor that is stored in the object. But to make this work well I want to move the options when I can but copy when I have to. It seems like I have to double up the number of constructors to support both move and copy of the options.

class Options {};

class Obj
{
    Options options;

    public:
        Obj(Options const& o, int x, int y):          options(o) {}
        Obj(Options const& o, std::string const& str):options(o) {}
        Obj(Options const& o, char v, int s)         :options(o) {}

        Obj(Options&& o, int x, int y):          options(std::move(o)) {}
        Obj(Options&& o, std::string const& str):options(std::move(o)) {}
        Obj(Options&& o, char v, int s)         :options(std::move(o)) {}
};

If I had used templates I could have used perfect forwarding and got the correct affect.

template<typename Options>
class Obj
{
    Options options;

    public:
        Obj(Options&& o, int x, int y):          options(std::forward<Options>(o)) {}
        Obj(Options&& o, std::string const& str):options(std::forward<Options>(o)) {}
        Obj(Options&& o, char v, int s)         :options(std::forward<Options>(o)) {}
};

The trouble with this is that I know the type Options.

2 Answers

Forwarding references only work with templates, you could templatize the constructors and impose restriction on template parameter.

class Obj
{
    Options options;
    template<typename Opt>
    using ValidOption = std::enable_if_t<std::is_same_v<Options, std::decay_t<Opt>>, bool>;


    public:
        template <typename X = Options, ValidOption<X> = true>
        Obj(X&& o, int x, int y):          options(std::forward<Options>(o)) {}
        template <typename X = Options, ValidOption<X> = true>
        Obj(X&& o, std::string const& str):options(std::forward<Options>(o)) {}
        template <typename X = Options, ValidOption<X> = true>
        Obj(X&& o, char v, int s)         :options(std::forward<Options>(o)) {}
};

Your last snippet:

template <typename Options>
class Obj
{
    Options options;

public:
    Obj(Options&& o, int, int):            options(std::forward<Options>(o)) {}
    Obj(Options&& o, std::string const&) : options(std::forward<Options>(o)) {}
    Obj(Options&& o, char, int)          : options(std::forward<Options>(o)) {}
};

is not perfect forwarding, as it is your class which is template, not your function.

Perfect forwarding would be:

class Obj
{
    Options options;

public:

    template <typename T> Obj(T&& o, int, int):            options(std::forward<T>(o)) {}
    template <typename T> Obj(T&& o, std::string const&) : options(std::forward<T>(o)) {}
    template <typename T> Obj(T&& o, char, int)          : options(std::forward<T>(o)) {}
};

I summarize (performance) options in Taking sink parameters by rvalue reference instead of by value to enforce performant usage of interfaces

So unless performance is really important and even an extra move is important, I would go for

Obj(Options o, int x, int y) : options(std::move(o)) {}

or

Obj(Options&& o, int x, int y) : options(std::move(o)) {}

(depending how important you want to avoid implicit copy).

If you target performance, you have indeed to go with forwarding reference.

Related