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?
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.