Каково намерение конструктора forward-by-lvalue-reference, пока существует совершенный конструктор пересылки?
Давайте принимать std::pair<T1, T2>
В качестве примера. Он имеет следующие два конструктора:
constexpr pair( const T1& x, const T2& y ); // #1
template< class U1, class U2 > constexpr pair( U1&& x, U2&& y ); // #2
Кажется, что #2 может обрабатывать все случаи, которые #1 может обрабатывать (без худшей производительности), за исключением случаев, когда аргумент является инициализатором списка. Например,
std::pair<int, int> p({0}, {0}); // ill-formed without #1
Итак, мой вопрос:
Если #1 предназначен только для аргумента list-initializer, так как
x
а такжеy
наконец, привязка к временным объектам, инициализированным из списков-инициализаторов, почему бы не использоватьconstexpr pair( T1&& x, T2&& y );
вместо?В противном случае, каково реальное намерение № 1?
2 ответа
Что если объект, который вы хотите сохранить, является временным, но не является подвижным?
#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());
}
Приведенный выше код не компилируется, потому что так называемый "идеальный" конструктор пересылки my_pair
пересылает временный test
объект как ссылка на значение, которое в свою очередь пытается вызвать явно удаленный конструктор перемещения test
,
Если мы удалим комментарий из my_pair
это не так "идеальный" конструктор, он предпочитает разрешение перегрузки и в основном вынуждает копию временного test
объект и тем самым заставляет его работать.
Вы ответили на свой вопрос.
pair( const T1& x, const T2& y );
был там до того, как конструктор пересылки был добавлен в C++17. constexpr
был добавлен в C++14. Теперь зачем держать конструктор рядом? Две причины: 1) обратная совместимость и 2) потому что, как вы сказали, совершенно правильно сформированный код не скомпилируется. C++ уделяет большое внимание обратной совместимости и использует очень консервативный подход к устареванию кода, потому что он может привести к непредсказуемым последствиям.
Теперь, если вы хотите более реалистичный пример, попробуйте std::for_each
, Вы можете изменить его так, чтобы он идеально передавал функтор без каких-либо ошибок в коде. Тем не менее, нет особых стимулов для добавления этого изменения, поскольку указано, что он должен делать только одну копию. Другими словами, изменение не очень высокий приоритет.