Сложное имя атрибута С++ для шаблона участника
Я обнаружил, что при доступе к атрибуту non-template (v.foo
) из переменной типа шаблона (T& v
), С++ можно обмануть в мысли, что это шаблон-член, если есть шаблон функция с тем же именем (template class <T> void foo()
). Как это можно объяснить из спецификации С++? Рассмотрим эту простую программу:
#include <cassert>
/** Determine whether the 'foo' attribute of an object is negative. */
template <class T>
bool foo_negative(T& v)
{
return v.foo < 0;
}
struct X
{
int foo;
};
int main()
{
X x;
x.foo = 5;
assert(!foo_negative(x));
return 0;
}
У нас есть функция шаблона foo_negative
, которая принимает объект любого типа и определяет, является ли его атрибут foo отрицательным. Функция main
создает foo_negative
с помощью [T = X]. Эта программа компилируется и запускается без вывода.
Теперь добавьте эту функцию в начало программы:
template <class T>
void foo()
{
}
Компиляция с помощью g++ 4.6.3 приводит к ошибке этого компилятора:
funcs.cpp: In function ‘bool foo_negative(T&)’:
funcs.cpp:13:14: error: parse error in template argument list
funcs.cpp: In function ‘bool foo_negative(T&) [with T = X]’:
funcs.cpp:25:5: instantiated from here
funcs.cpp:13:14: error: ‘foo’ is not a member template function
(где строка 13 - return v.foo < 0
, а строка 25 - assert(!foo_negative(x))
.)
Clang производит похожие ошибки.
Wat? Как добавление несвязанной функции, которая никогда не называется, позволяет ввести синтаксическую ошибку в действительную программу? При анализе foo_negative
компилятор не знает тип v
, и, самое главное, он не знает, является ли v.foo
шаблоном-членом или регулярным членом. По-видимому, он должен решить во время разбора (до создания шаблона), следует ли рассматривать его как шаблон-член или регулярный член.
Если он считает, что v.foo
является шаблоном-членом, то < 0
рассматривается как передача 0
в качестве аргумента шаблона, а отсутствует >
, следовательно, синтаксическая ошибка. Затем, когда foo_negative
создается с помощью [T = X], возникает еще одна ошибка, поскольку X::foo
не является шаблоном-членом.
Но почему он думает, что v.foo
является шаблоном-членом? Эта двусмысленность - это именно то, к чему относится ключевое слово template
: если я написал v.template foo
, тогда я бы прямо сказал С++ ожидать шаблон-член, но я не использовал ключевое слово template
! Я не ссылался на шаблон участника, поэтому он должен считать, что он является регулярным членом. Тот факт, что существует функция с тем же именем, что и член, не должен иметь никакого эффекта. Почему? Это не может быть ошибкой в компиляторе, потому что GCC и clang согласованы.
Ответы
Ответ 1
В Clang это было PR11856, которое было исправлено ~ 2,5 месяца назад. Clang trunk и Clang 3.1 не сообщают никаких ошибок с этим кодом. Ошибка Clang включает пояснение о том, почему этот код был отклонен, и почему код верен, воспроизведен здесь (слегка измененный для решения вашего дела)
Этот параграф имеет значение [basic.lookup.classref] p1:
"В выражении доступа к члену класса (5.2.5), если токен .
или ->
сразу же следует идентификатор, за которым следует <
, идентификатор должны быть проверены, чтобы определить, является ли <
началом список аргументов шаблона (14.2) или меньше, чем оператор. Идентификатор сначала просматривается в классе выражения объекта. Если идентификатор не найден, он затем просматривается в контексте целое постфиксное выражение и назовем шаблон класса."
Так как v
зависит, по-видимому, идентификатор не найден, поэтому мы подумайте, что произойдет, если мы посмотрим в контексте всего постфикс-выражение. Поскольку мы находим шаблон функции, мы не должны заключаем, что у нас есть начало идентификатора шаблона.
Ответ 2
Это похоже на ошибку компилятора.
В стандарте говорится:
После поиска имени (3.4) обнаружено, что имя является именем шаблона или идентификатор оператора-функции или идентификатор литерала-оператора ссылается на набор перегруженные функции, любой из которых является шаблоном функции, если за этим следует <, < всегда принимается за разделитель template-argument-list и никогда не меньше, чем оператор.
и в 3.4.5/1
В выражении доступа к члену класса (5.2.5), если. или → токен после чего следует идентификатор, за которым следует идентификатор <, идентификатор должны быть проверены, чтобы определить, является началом список аргументов шаблона (14.2) или меньше, чем оператор. Идентификатор сначала просматривается в классе выражения объекта. Если идентификатор не найден, он затем просматривается в контексте всего постфиксного выражения и назовем шаблон класса.
Стандарт, похоже, не указывает, что поиск имени может когда-либо найти шаблон функции, не являющийся членом. В любом случае значение <
должно определяться во время определения шаблона, а не времени создания (это слишком поздно).
Ответ 3
Это ошибка.
В MSVC (2011) ваш код работает нормально.
Я думаю, что синтаксический анализатор компилятора перевел '<' как начальный токен для оператора шаблона. Но почему у Clang и GCC есть эта ошибка в то время?
Сообщите об ошибке здесь и здесь.
Возможно, это тоже интересно:
Еще одна ошибка в g++/Clang? [Шаблоны С++ интересны]