Search code examples
c++constructormove-semanticsrvalue-referenceperfect-forwarding

What's the intention of forward-by-lvalue-reference constructor while a perfect forwarding constructor exists?


Let's take std::pair<T1, T2> as an example. It has the following two constructors:

constexpr pair( const T1& x, const T2& y );                      // #1
template< class U1, class U2 > constexpr pair( U1&& x, U2&& y ); // #2

It seems that #2 can handle all cases that #1 can handle (without worse performance), except for cases where an argument is a list-initializer. For example,

std::pair<int, int> p({0}, {0}); // ill-formed without #1

So my question is:

  • If #1 is only intended for list-initializer argument, since x and y finally bind to temporary objects initialized from list-initializers, why not use constexpr pair( T1&& x, T2&& y ); instead?

  • Otherwise, what's the actual intention of #1?


Solution

  • What if the object you want to store is a temporary one but is not movable ?

    #include <type_traits>
    #include <utility>
    #include <iostream>
    
    class   test
    {
    public:
      test() { std::cout << "ctor" << std::endl; }
      test(const test&) { std::cout << "copy ctor" << std::endl; }
      test(test&&) = delete; // { std::cout << "move ctor" << std::endl; }
      ~test() { std::cout << "dtor" << std::endl; }
    
    private:
      int dummy;
    };
    
    template <class T1, class T2>
    class   my_pair
    {
    public:
      my_pair() {}
      // Uncomment me plz !
      //my_pair(const T1& x, const T2& y) : first(x), second(y) {}
      template <class U1, class U2, class = typename std::enable_if<std::is_convertible<U1, T1>::value && std::is_convertible<U2, T2>::value>::type>
      my_pair(U1&& x, U2&& y) : first(std::forward<U1>(x)), second(std::forward<U2>(y)) {}
    
    public:
      T1 first;
      T2 second;
    };
    
    int     main()
    {
      my_pair<int, test>    tmp(5, test());
    }
    

    The above code doesn't compile because the so called "perfect" forwarding constructor of my_pair forwards the temporary test object as an rvalue reference which in turn tries to call the explicitly deleted move constructor of test.

    If we remove the comment from my_pair's not so "perfect" constructor it is preferred by overload resolution and basically forces the copy of the temporary test object and thus makes it work.