Вывел конфликтующие типы для параметра '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));
}

Возврат контейнера больше не нужен...

Другие вопросы по тегам