Вывел конфликтующие типы для параметра 'T' для универсальной ссылки
Я тестирую универсальную ссылку со следующим кодом,
template <typename T>
vector<T> attach_(vector<T> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
int main() {
int k = 2;
attach_(std::move(vector<int>{1,2,3}),k); //not OK
attach_(std::move(vector<int>{1,2,3}),(int&)k); //not OK
attach_(std::move(vector<int>{1,2,3}),(int)k); //OK
attach_(std::move(vector<int>{1,2,3}),2); //OK
}
и получил ошибку:
no matching function for call to 'attach_(std::remove_reference<std::vector<int> >::type, int&)'
attach_(std::move(vector<int>{1,2,3}),k);
note: template argument deduction/substitution failed:
note: deduced conflicting types for parameter 'T' ('int' and 'int&')
attach_(std::move(vector<int>{1,2,3}),k);
У SO возникает вопрос об аналогичной ошибке. Сообщение об ошибке "выведены конфликтующие типы для параметра" const T "" о ссылках на const.
Я также протестировал несколько других случаев, некоторые с преобразованием типов. Некоторые работают, а другие нет.
Я слышал, что универсальные ссылки, такие как T&&
соответствует всему. Почему это терпит неудачу здесь?
Второй вопрос, как набрать attach_
чтобы убедиться, что семантика перемещения работает как для xs
а также x
для соответствующего ввода? В конечном счете, я хотел бы иметь вариант следующего:
for(int i = 0; i < 100; i++)
xs = attach_(xs,values[i])
работать без создания ненужных копий.
(Это проверено с gcc4.8.1, используя g++ -std= C++11 test.cpp)
Спасибо
-- РЕДАКТИРОВАТЬ ---
Спасибо всем за отличные ответы.
Итак, теперь я понимаю, что для этого случая целесообразно просто использовать передачу по значению и переместить T
, Предполагается, что вектор xs не копируется без необходимости при передаче параметров и возврате назад, если используется в цикле, верно?
Я задал связанный вопрос. Когда константная ссылка лучше, чем передача по значению в C++11?, Там у меня был такой пример, где все говорили, что прохождение через реку - плохая идея:
int hd(vector<int> a) {
return a[0];
}
Можно ли вообще использовать универсальную ссылку для обработки как hd
случай и attach_
Случай в этом посте, чтобы избежать ненужных копий?
Еще раз спасибо.
--- EDIT2 ---
Итак, я проверил версии в ответах плюс const
Ссылочная версия ниже. Оптимизация не используется для выявления потенциальных проблем. const
Ссылочная версия является худшей, поскольку она вызывает копирование. Все остальное имеет такую же скорость, если std::move(a)
используется для вектора, кроме необработанного push_call
звонки быстрее. Я думаю, что оптимизация может устранить эту разницу. Я думаю, что тест (или, возможно, тип int) не достаточно велик, чтобы показать разницу между push_back(x)
а также push_back(std::move(x))
#include <vector>
#include <iostream>
#include <chrono>
using namespace std;
template <class T>
vector<T> attach(vector<T> v, T x) {
v.push_back(x);
return v;
}
template <typename T>
vector<T> attach1(vector<T> xs, T x) {
xs.push_back(std::move(x));
return xs;
}
template <typename T, typename E = typename std::remove_reference<T>::type>
std::vector<E> attach2(std::vector<E> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
template <typename C, typename T> C attach3(C&& xs, T&& x) {
xs.push_back(std::move<T>(x));
return std::forward<C>(xs);
}
template <class T>
vector<T> attach4(const vector<T>& v, T x) {
vector<T> ret = v;
ret.push_back(x);
return std::move(ret);
}
using namespace std::chrono;
int main() {
int N = 100000;
vector<int> a;
auto time = high_resolution_clock::now();
for (int i = 0; i < N; i++) {
//a.push_back(i); //0s
//a = attach(a,i); //15s
//a = attach(std::move(a),i); //0.03s
//a = attach2(std::move(a),i); //0.03s
a = attach3(std::move(a),i); //0.03s
//a = attach4(std::move(a),i); //14.9s
}
cout << duration_cast<duration<double>>(high_resolution_clock::now() - time).count() << endl;
}
3 ответа
Принцип работы универсальных ссылок таков: если передать значение r, T
будет выведено как int
(или некоторый другой не-ссылочный тип), потому что тогда T&&
является ссылочным типом rvalue. Но если вы перейдете в lvalue, то T
будет выведено как int&
(или некоторый другой ссылочный тип lvalue), потому что тогда T&&
будет ссылочным типом lvalue (поскольку ссылка lvalue и ссылка rvalue "сворачиваются" вместе в ссылку lvalue).
Так что в случае, если вы передаете в lvalue, у вас есть проблема, потому что вы не можете иметь vector<T>
когда T
является ссылочным типом
Вы должны просто передать по значению,
template <typename T>
std::vector<T> attach_(std::vector<T> xs, T x) {
xs.push_back(std::move(x));
return xs;
}
Это может выглядеть менее эффективно, но это не так. Если вы передадите в rvalue, он будет перемещен один раз в x
и переместился еще раз в вектор. Если вы передадите значение lvalue, оно будет скопировано один раз в x
и они переместились в вектор. Это так же, как если бы вы передали по ссылке: одна копия для lvalue, ноль копий для rvalue.
В образовательных целях вы можете сделать это со своей универсальной ссылкой:
template <typename T, typename E = typename std::remove_reference<T>::type>
std::vector<E> attach_(std::vector<E> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
Это гарантирует, что при передаче lvalue тип векторного элемента является не ссылочным типом. Но на самом деле лучше просто передать по значению.
Я слышал, что универсальные ссылки, такие как T&&, соответствуют всему. Почему это терпит неудачу здесь?
Причина, по которой универсальные ссылки соответствуют всем, заключается в том, что правила вывода аргументов шаблона говорят, что когда T &&
в паре с lvalue типа X
Т выводится, чтобы быть X&
, а потом ссылочный коллапс делает X& &&
становиться X&
,
В attach_(std::move(vector<int>{1,2,3}),k);
Компилятор выводит T
как int
из первого параметра (vector<T>
<-> vector<int>
), а из второго параметра T выводится как int &
поскольку k
это значение. Таким образом вы получаете ошибку.
Второй вопрос, как набрать
attach_
чтобы убедиться, что семантика перемещения работает как дляxs
а такжеx
для соответствующего ввода?
Простейшим было бы просто передать его по значению и переместить в вектор:
template <typename T>
vector<T> attach_(vector<T> xs, T x) {
xs.push_back(std::move(x));
return xs;
}
x
будет по-прежнему построено на ходу, если оно прошло с std::move
,
Редактировать: если вы работаете с устаревшим типом с большими копиями, создание двух копий, как в приведенном выше случае, не является идеальным. В этом случае вы можете сделать то, что @Deduplicator показал в своем ответе:
template <typename T>
vector<typename std::remove_reference<T>::type>
attach_(vector<typename std::remove_reference<T>::type> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
Изменить 2:
Итак, теперь я понимаю, что для этого случая целесообразно просто использовать передачу по значению и переместить
T
, Примите это, что векторxs
не копируется без необходимости при передаче параметров и возвращении обратно, если используется в цикле, верно?
Общее правило: "передавайте по значению, если оно маленькое или если вам все равно нужно сделать копию, и по ссылке в противном случае". В attach_
нужно сделать копию x
(от push_back
вставляя его в вектор), так что передавать его по значению, а затем перемещать - это нормально.
Нужно ли передавать вектор по значению, зависит от вашей предполагаемой семантики. Если attach_(xs, x)
не должен изменять xs
, тогда вам нужно будет сделать копию вектора для возврата в любом случае, и, следовательно, вы должны передать его по значению. Тем не менее, когда вы делаете xs = attach_(xs, x);
, вы понесете копию. xs = attach_(std::move(xs), x);
не будет иметь копии, но имеет некоторые небольшие дополнительные издержки от конструкции перемещения, сопровождаемой назначением перемещения.
Если attach_(xs, x)
должен изменить xs
, а затем передать его по неконстантной ссылке. Никаких накладных расходов.
Можно ли вообще использовать универсальную ссылку для обработки как
hd
случай иattach_
Случай в этом посте, чтобы избежать ненужных копий?
hd
не нуждается в универсальной ссылке. Вы просто индексируете вектор, поэтому просто передайте его по константной ссылке.
Универсальная ссылочная семантика работает по этой цитате:
8.3.2 Ссылки §6
Если typedef-name (7.1.3, 14.1) или decltype-спецификатор (7.1.6.2) обозначает тип TR, который является ссылкой на тип T, попытка создать тип "lvalue ссылка на cv TR" создает введите "lvalue reference to T", а попытка создать тип "rvalue reference to cv TR" создает тип TR. [ Пример:
int i; typedef int& LRI; typedef int&& RRI; LRI& r1 = i; // r1 has the type int& const LRI& r2 = i; // r2 has the type int& const LRI&& r3 = i; // r3 has the type int& RRI& r4 = i; // r4 has the type int& RRI&& r5 = 5; // r5 has the type int&& decltype(r2)& r6 = i; // r6 has the type int& decltype(r2)&& r7 = i; // r7 has the type int&
- конец примера]
Разумное использование std::remove_reference
решит вашу ошибку легко:
#include <vector>
using namespace std;
template <typename T>
vector<typename std::remove_reference<T>::type>
attach_(vector<typename std::remove_reference<T>::type> xs, T&& x) {
xs.push_back(std::forward<T>(x));
return xs;
}
int main() {
int k = 2;
attach_(std::move(vector<int>{1,2,3}),k); //now OK
attach_(std::move(vector<int>{1,2,3}),(int&)k); //now OK
attach_(std::move(vector<int>{1,2,3}),(int)k); //OK
attach_(std::move(vector<int>{1,2,3}),2); //OK
}
В любом случае, вы уверены, что хотите передать контейнер по значению?
Кроме того, почему бы не дать ему собственный параметр шаблона, чтобы сделать функцию более общей?
template <typename T, typename C> void attach_(C& xs, T&& x) {
xs.push_back(std::forward<T>(x));
}
Возврат контейнера больше не нужен...