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

2

7

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?

Muth answered 4/1, 2018 at 9:16 Comment(0)
S
3

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.

Singletree answered 4/1, 2018 at 11:20 Comment(0)
H
1

You answered your own question.

pair( const T1& x, const T2& y ); was there before the forwarding constructor was added in C++17. The constexpr was added in C++14. Now why keep the constructor around? Two reasons: 1) backwards compatibility and 2) because as you said, perfectly well-formed code would refuse to compile. C++ has a heavy emphasis on backwards compatibility and takes a very conservative approach to deprecating code because it can break in unexpected ways.

Now if you want a more realistic example, try std::for_each. You can change it to perfect forward a functor without any breakage in code. However, there's little incentive to add this change because it's specified to make only exactly one copy. In other words, the change is not very high priority.

Hopscotch answered 4/1, 2018 at 10:22 Comment(1)
The forwarding constructor was added in C++11. I don't think std::for_each is a good example because functor is often cheap to copy.Muth

© 2022 - 2024 — McMap. All rights reserved.