Справочная интерпретация в С++ - программирование
Подтвердить что ты не робот

Справочная интерпретация в С++

Рассмотрим следующий код:

#include <iostream>

template<typename T>
void inc1(T&& x)
{
    T y = x;
    ++y;
}

template<typename T>
void inc2(T& x)
{
    T y = x;
    ++y;
}

int main()
{
    int a = 10;
    inc1(a); // a is now 11

    int b = 10;
    inc2(b); // b remains 10
}

После подстановки имеем

void inc1(int& x)
{
    int& y = x; // reference to x
    ++y; // increments x
}

void inc2(int& x)
{
    int y = x; // copy of x
    ++y; // increments the copie
}

В inc1, x имеет тип int&, потому что оба int& и T&& являются ссылками, но не оба значения r.

Аналогично, в inc2, x имеет тип int&, потому что снова оба int& и T& являются ссылками, но не оба значения r.

Мой вопрос о y: почему в inc1, y имеет тип int&, а в inc2, y имеет тип int?

Я наблюдал это как на gcc 4.8.1, так и на microsoft v110 и v120_ctp.

4b9b3361

Ответ 1

В обоих вызовах функций то, что вы передаете функции, является int & (в смысле: "lvalue типа int" ). Итак, с объявлением inc1, компилятор должен вывести T таким образом, чтобы T && соответствовал предоставленному аргументу, т.е. int &. Единственный способ сделать это - предположить, что T есть int &, потому что тогда T && есть int & &&, что эквивалентно int &. Таким образом, T становится int &, а локальный y объявляется как таковой.

С другой стороны, в inc2 компилятор должен выводить T таким образом, чтобы T & соответствовал указанному типу аргументов, который по-прежнему int &. Это проще всего сделать, если предположить, что T - это просто int, так что вы получите для типа локального y тогда.


Ответ на несколько комментариев (которые тем временем были удалены): Если у вас есть функция с предопределенным типом аргументов, например

inc3(int x) { /*...*/ }

тогда, когда вы вызываете это, например, как inc3(a), компилятор применит любое неявное преобразование, необходимое для аргумента, чтобы он соответствовал. В случае inc3(a) это означает преобразование a из int & (в смысле lvalue) в int (в смысле rvalue) – который называется преобразованием lvalue-to-rvalue и действительным неявным преобразованием. Это в основном сводится к преобразованию переменной a в значение, которое оно представляет в это время.

Но когда вы объявляете шаблон, например inc1 и inc2, из вопроса, а аргумент функции определяется с помощью параметра шаблона, тогда компилятор не будет или не только попытается применить неявное конверсии в аргумент, чтобы он соответствовал. Вместо этого он выберет параметр типа аргумента T, чтобы он соответствовал указанному типу аргументов. Правила для этого сложны, но в случае объявления аргумента типа T && они работают, как описано выше. (В случае объявления аргумента pure T аргумент lvalue все равно будет проходить преобразование lvalue-to-rvalue, а T будет выводиться как int, а не int &.)

Вот почему, хотя int && является ссылкой на rvalue, T && (где T является параметром шаблона) не обязательно является ссылкой rvalue. Вместо этого, любой результат от установки T к предоставленному аргументу. Таким образом, выражение T && в этом случае было названо универсальной ссылкой (в отличие от ссылки lvalue или rvalue) – это ссылка, которая при необходимости становится либо lvalue, либо rvalue.

Ответ 2

S14.8.2.1 [temp.deduct.call] говорит:

Вывод аргумента шаблона производится путем сравнения каждого параметра параметра шаблона функции (вызов его P) с помощью тип соответствующего аргумента вызова (назовите его A), как описано ниже.

Итак, мы пытаемся выработать P заданного типа A int.

S14.8.2.3 продолжается:

Если P является классом с квалификацией cv, верхние уровни cv-квалификаторы типа Ps игнорируются для вывода типа. Если P является ссылочный тип, тип, обозначаемый P, используется для вычитания типа. Если P является ссылкой rvalue на cv-неквалифицированный параметр шаблона, и аргумент является lvalue, вместо слова A для типа используется тип "lvalue reference to A". [Пример:

template <class T> int f(T&&);         // <--- YOUR TEMPLATE IS LIKE THIS
template <class T> int g(const T&&);
int i;
int n1 = f(i); // calls f<int&>(int&)  // <--- YOUR CALL IS LIKE THIS
int n2 = f(0); // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which
               // would bind an rvalue reference to an lvalue

-end пример]

Ваш вызов похож на f(i) в примере - который создает функцию формы f<int&>(int&)... т.е. T is int&, поэтому T y = x создает ссылку на x.

См. также страницу Скотта Мейера в http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers