Правильный способ использовать decltype в качестве конечного типа возвращаемого значения

#c #templates #c 11 #decltype #perfect-forwarding

#c #шаблоны #c 11 #decltype #идеальная пересылка

Вопрос:

Я очень часто вижу пример этой формы:

 template <typename T, typename U>
auto add(Tamp;amp; t, Uamp;amp; u) -> decltype(std::forward<T>(t)   std::forward<U>(u))
{
    return std::forward<T>(t)   std::forward<U>(u);
}  
  

но я осмелюсь сказать, что это лучший, более правильный способ:

 template <typename T, typename U>
auto add(Tamp;amp; t, Uamp;amp; u) -> decltype(t   u)//no forwarding here
{
    return std::forward<T>(t)   std::forward<U>(u);
}
  

Почему? Прежде всего, decltype в этом примере должен только определять тип возвращаемого значения, поэтому (t u) не является типом возвращаемого значения (std::forward(t) std::forward(u)), второй код, сгенерированный двумя версиями, идентичен, а третий decltype (u t) более прямой и точно выражает намерения программиста, не раскрывая «внутренности» реализации.

Каково ваше мнение по этому вопросу?

Ответ №1:

Первая версия более правильная, потому что она точно соответствует тому, что должно возвращать тело функции. Как уже указывалось, просто нет гарантии, что

 decltype(std::forward<T>(t)   std::forward<U>(u))
  

будет того же типа, что и

 decltype(t   u)
  

Возможно, это довольно сложный случай, но «правильный» способ — использовать std::forward .

Ответ №2:

В общем, я не могу придумать разумный вариант использования, где будет разница, но я предполагаю, что вы могли бы найти какой-то случай, когда операция имеет разные реализации для rvalue-ссылок и rvalues, и правила языка не предписывают, что возвращаемый тип для разных перегрузок должен быть одинаковым (даже если это диктует здравый смысл).

Итак, в общем случае разницы не будет, а в случае, когда различия есть, что ж, в этих случаях требуется дополнительная осторожность и внимание для решения гораздо более серьезных проблем, чем сам шаблон…

 // sick corner case:
struct type {};
int operator ( typeamp;amp; lhs, typeamp;amp; rhs );
double operator ( type const amp; lhs, type const amp; rhs );
  

Я могу подумать о ситуациях, когда вы хотели бы предложить различные перегрузки для ссылок на значения (рассмотрим некоторую реализацию списка, который предлагает operator в качестве конкатенации, тогда перегрузка ссылками на значения могла бы избежать затрат на копирование, просто изменив указатели и оставив списки аргументов пустыми), но было бы крайне запутанно, если бы тип результата зависел от l / rvalue-ness аргументов.

Комментарии:

1. чем больше я использую decltype, тем больше я убежден, что он должен работать с типами, а не с объектами. Потому что более естественно думать о том, что является результатом добавления двух типов вместо двух объектов этих типов.

2. @Мы ничего не можем сделать: я не совсем согласен с этим, в использовании объектов вместо типов есть явное преимущество: компилятор выполнит любое требуемое преобразование аргументов: template <typename T, typename U> auto sum( T lhs, U rhs ) -> decltype( lhs rhs ) { return lhs rhs; } преобразует в большее из T и U для арифметических типов. template <typename T> auto foo( T x ) -> decltype( foo_(x) ) { return foo_(x); } выполнит любое неявное преобразование, которое может потребоваться, от фактического T к наилучшей перегрузке foo_ из доступных…

3. Я предполагаю, что вы также могли бы подумать, что это decltype( foo( int ) ) можно использовать в грамматике, но это создало бы первый случай id( type ) , который не является идеальным совпадением (во всех других случаях преобразования из int не может быть) и, вероятно, привело бы к путанице. О каком foo из них вы говорите? У меня есть только foo(doble) и foo(string) !

Ответ №3:

Внутри decltype(t u) переменные t и u уже не являются ссылками на rvalue, они будут обрабатываться как простые ссылки на lvalue, следовательно, вам нужно дополнительное std::forward . (По крайней мере, я так это понимаю. Хотя это может быть и неправильно.)

Комментарии:

1. но это только для того, чтобы вывести тип retur, ничего больше, вы ничего не делаете с этими переменными в decltype.

2. @There: Это все еще может быть важно, поскольку может возникнуть особая перегрузка operator ссылок на rvalue.