Search code examples
c++perfect-forwarding

Perfect forwarding trickery


If your object has multiple parameters and can take l or r value references the number of constructors jumps quickly. Can a little template trickery simplify the situation or does this cause issues that I am not aware of OR am I over-thinking it and there is standard way of simplifying this?

// In the following context I using std::string as an example o
// something that can be moved or copied, where we would prefer
// a move rather than a copy.

// With just two parameters.
// I need 4 constructors to do the correct thing (copy/move) correctly.
class Original
{
    std::string   val1;
    std::string   val2;
    public:
        Original(std::string const& v1, std::string const& v2): val1(v1), val2(v2) {}
        Original(std::string const& v1, std::string&&      v2): val1(v1), val2(std::move(v2)) {}            
        Original(std::string&&      v1, std::string const& v2): val1(std::move(v1)), val2(v2) {}            
        Original(std::string&&      v1, std::string&&      v2): val1(std::move(v1)), val2(std::move(v2)) {}
};

If the parameter types were templates I could use perfect forwarding.

// This is a check to see if a template is the specific class.
// Or can be trivially constructed from the parameters.
template<typename Actual>
using ValidString = std::enable_if_t<std::is_trivially_constructible_v<std::string, Actual>, bool>;


// With these I can simplify my requirement of 4 constructors
// and simplify to a single templaed constructor that can do perfect forwarding.
class Alternative
{
    std::string   val1;
    std::string   val2;
    public:
        template<typename V1, typename V2, ValidString<V1> = true, ValidString<V2> = true>
        Original(V1 v1, V2 v2): val1(std::forward<V1>(v1)), val2(std::forward<V2>(v2)) {}
};

Two questions:

  1. Is there already a technique to allow perfect forwarding of parameters that I missed?
  2. If there is not a current technique, what are the issues here?

Solution

  • The simplest way to do this in your specific case would be to do:

    class Original
    {
        std::string val1;
        std::string val2;
    public:
        Original(std::string v1, std::string v2) noexcept
          : val1{ std::move(v1) }
          , val2{ std::move(v2) }
        {}
    };
    

    This is often good enough for constructors. It potentially does copy+move instead of just a copy, but most types are cheap to move anyway. One case where you may have to do your version is when the assignment is conditional. Something like:

    template<typename T, typename Y>
    void foo(T&& t, Y&& y)
    {
       if (condition)
       {
          varT = std::forward<T>(t);
          varY = std::forward<Y>(y);
       }
    }
    

    You can't get away with copy+move, because you definitely don't want an unnecessary copy.