Свертывание ссылок C++98/03 и квалификаторы cv

Приведенный ниже код компилируется (gcc 4.7.2 или icc 13) и выдает результат «1 2». Это означает, что квалификатор const отбрасывается, т.е. е., f<int&> имеет тип параметра int&.

Почему это происходит? Насколько я понимаю, согласно §14.3.1.4:

Если аргумент шаблона для параметра шаблона T называет тип «ссылка на cv1 S», попытка создать тип «ссылка на cv2 T» создает тип «ссылка на cv12 S», где cv12 — это объединение cv-квалификаторов cv1 и cv2. Избыточные квалификаторы cv игнорируются.

const нельзя отбрасывать. Вот код:

#include <iostream>
using namespace std;

template <typename T>
void f(const T& t)
{
    t++;
}

int main()
{
    int a = 1;

    cout << a;
    f<int&>(a);
    cout << ' ' << a << endl;

    return 0;
}

person user2052436    schedule 07.02.2013    source источник
comment
Я не могу найти эту цитату ни в C++98, ни в C++03. §14.3.1 представляет собой аргументы шаблонного типа и не имеет подраздела или параграфа 4.   -  person Joseph Mansfield    schedule 08.02.2013


Ответы (2)


GCC 4.7.2 не компилирует это, если указан флаг -std=c++98. На самом деле в C++98 (как и в C++03) ссылки на ссылки не свертываются.

Попытка создать экземпляр f<int&>, где T = int&, приводит к следующей сигнатуре функции (здесь я намеренно поменял местами тип аргумента T и спецификатор const, что разрешено, поскольку const T& совпадает с T const&):

void f(int& const& t) // ERROR: reference to reference is illegal

Вышеприведенное недопустимо ни в C++98, ни в C++03. Соответственно, это ошибка, которую вы получаете от GCC 4.7.2:

Compilation finished with errors:
source.cpp: In function 'int main()':
source.cpp:15:14: error: no matching function for call to 'f(int&)'
source.cpp:15:14: note: candidate is:
source.cpp:5:6: note: template<class T> void f(const T&)
source.cpp:5:6: note:   template argument deduction/substitution failed:
source.cpp: In substitution of 'template<class T> void f(const T&) [with T = int&]':
source.cpp:15:14:   required from here
source.cpp:5:6: error: forming reference to reference type 'int&'

Тем не менее, если вы используете флаг -std=c++11, компилятор выполняет свертывание ссылок при создании экземпляра шаблона: ссылка lvalue на ссылку lvalue становится ссылкой lvalue:

void f(int& const& t) == void f(int& t)

Здесь квалификатор const отбрасывается, поскольку он применяется к ссылке, а не к объекту, на который указывает ссылка. Так как ссылки нельзя переназначить, они по своей природе const, поэтому const считаются лишними и удаляются. См. эти вопросы и ответы по SO для объяснения.

Это дает ссылку lvalue на ссылку lvalue, которая разрешается в простую ссылку lvalue. Таким образом, создается экземпляр подписи с правой стороны.

Приведенное выше является подходящим кандидатом для разрешения вызова f<int&>(a) и, следовательно, компилируется без ошибок.

person Andy Prowl    schedule 07.02.2013
comment
Как void f(const int& t) { t++; } скомпилировать? Он изменяет t, который является константной ссылкой. - person Kleist; 08.02.2013
comment
Вместо того, чтобы предполагать c++11 по умолчанию, приведенный выше код некоторое время работал в gcc как расширение (он компилируется в режиме по умолчанию в 4.1, но завершается ошибкой, если вы запрашиваете -ansi. - person David Rodríguez - dribeas; 08.02.2013
comment
@Kleist: функция в комментарии не должна компилироваться. - person David Rodríguez - dribeas; 08.02.2013
comment
@Kleist: я думаю, что исправил объяснение. - person Andy Prowl; 08.02.2013
comment
@DavidRodríguez-dribeas: Спасибо за разъяснения. Я удалил эту часть предложения и исправил объяснение. - person Andy Prowl; 08.02.2013
comment
@AndyProwl: Да, теперь это имеет смысл. Особенно ответ stackoverflow.com/a/4494126/164277 по вашей ссылке дает мне понять. - person Kleist; 08.02.2013

Вот 1770 год, откуда, по-видимому, и возникла рассматриваемая цитата:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1770.html

14.3.1 - Аргументы типа шаблона

-4- Если аргумент-шаблона для параметра-шаблона T называет тип "lvalue-ссылка на cv1 S", попытка создать тип "(lvalue или rvalue) ссылка на cv2 T" создает тип "lvalue-ссылка к cv12 S", где cv12 является объединением cv-квалификаторов cv1 и cv2. Если аргумент шаблона называет тип "rvalue-ссылка на cv1 S", попытка создать тип "lvalue-ссылка на cv2 T" создает тип "lvalue-ссылка на cv12 S". Если аргумент шаблона называет тип "rvalue-ссылка на cv1 S", попытка создать тип "rvalue-ссылка на cv2 T" создает тип "rvalue-ссылка на cv12 S". Избыточные квалификаторы cv игнорируются.

Вот 2118, где цитата зачеркнута:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2118.html

14.3.1 - Аргументы типа шаблона

-4- Если аргумент-шаблона для параметра-шаблона T называет тип "ссылка на cv1 S", который является ссылкой на тип A, попытка создать тип "ссылка to cv2 T" "lvalue-ссылка на cv T" создает тип "ссылка на cv12 S", где cv12 — это объединение cv-квалификаторов cv1 и cv2. Избыточные cv-квалификаторы игнорируются "lvalue-ссылка на A", а попытка создать тип "rvalue-ссылка на cv T" создает тип T.

То, что вы цитируете, кажется устаревшей формулировкой.

person Yakk - Adam Nevraumont    schedule 07.02.2013
comment
Примечание. В окончательной версии C++11 это оказалось в 8.3.2/5 (т.е. часть раздела dcl.ref, а не temp.arg.type). Формулировки не изменились, за исключением имен. - person jogojapan; 01.01.2015