Видимость члена класса в подписи объявления функции-члена
Почему это работает:
template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};
Но это не так (a
а также f
поменялись местами):
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
A a;
};
говоря это a
не объявляется в этой области (внутри decltype), но добавляется явное this->
заставляет это работать.
3 ответа
template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};
Это работает, потому что внутри конечного возвращаемого типа члены окружающего класса видны. Не все члены, а только те, которые объявлены до него (в конце типа возврата класс не считается завершенным, в отличие от тел функций). Итак, что здесь сделано:
- Поскольку мы находимся в шаблоне, выполняется поиск, чтобы увидеть,
a
зависит или нет. посколькуa
было объявлено доf
,a
найдено, чтобы обратиться к члену класса. По правилам шаблонов в C++ установлено, что
a
ссылается на член текущего экземпляра, так как он является членом экземпляров окружающего шаблона. В C++ это понятие используется главным образом для определения того, являются ли имена зависимыми: если известно, что имя ссылается на члены окружающего шаблона, его необязательно искать при создании экземпляра, поскольку компилятор уже знает код шаблона (который используется как основание для класса, созданного из него!). Рассматривать:template<typename T> struct A { typedef int type; void f() { type x; A<T>::type y; } };
В C++03 вторая строка объявляется y
было бы ошибкой, потому что A<T>::type
был зависимым именем и нуждался в typename
перед ней. Только первая строка была принята. В C++11 это несоответствие было исправлено, и оба типа имен независимы и не нуждаются в typename
, Если вы измените typedef на typedef T type;
тогда обе декларации, x
а также y
будет использовать зависимый тип, но ни один не будет нуждаться в typename
потому что вы все еще называете элемент текущего экземпляра, и компилятор знает, что вы называете тип.
- Так
a
является членом текущего экземпляра. Но это зависит, потому что тип используется для его объявления (A
) зависит. Однако это не имеет значения в вашем коде. Независимо от того, зависим или нет,a
найден и код действителен.
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
A a;
};
В этом коде снова a
ищется, чтобы увидеть, является ли он зависимым и / или является ли он членом текущего экземпляра. Но так как мы узнали выше, что члены, объявленные после завершающего возвращаемого типа, не видны, мы не можем найти объявление для a
, В C++, кроме понятия "член текущего экземпляра", есть другое понятие:
член неизвестной специализации. Это понятие используется для обозначения случая, когда имя может вместо этого ссылаться на члена класса, который зависит от параметров шаблона. Если бы мы получили доступ
B::a
тогдаa
будет членом неизвестной специализации, потому что неизвестно, какие объявления будут видны, когдаB
заменяется при создании экземпляра.Ни член действующего, ни член неизвестной специализации. Это касается всех других имен. Ваш случай подходит здесь, потому что известно, что
a
никогда не может быть членом какого-либо экземпляра, когда это происходит (помните, что поиск по имени не может найтиa
, так как он объявлен послеf
).
поскольку a
не зависит от какого-либо правила, поиск, который не нашел ни одного объявления, является обязательным, что означает, что нет другого поиска при создании экземпляра, который мог бы найти объявление. Независимые имена ищутся во время определения шаблона. Теперь GCC по праву выдает ошибку (но учтите, что, как всегда, неправильно сформированный шаблон не требует немедленной диагностики).
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(this->a.f(b))
{
}
A a;
};
В этом случае вы добавили this
и GCC принято. Имя a
что следует this->
снова ищем, может ли он быть членом текущего экземпляра. Но опять же, из-за видимости члена в конечных типах возвращаемых данных, объявление не найдено. Следовательно, имя считается не являющимся членом текущего экземпляра. Так как при реализации не существует способа, S
может иметь дополнительных членов, которые a
может соответствовать (нет базовых классов S
которые зависят от параметров шаблона), имя также не является членом неизвестной специализации.
Снова C++ не имеет правил, чтобы сделать this->a
зависимый. Однако он использует this->
поэтому имя должно относиться к какому-то члену S
когда он будет создан! Таким образом, стандарт C++ говорит
Точно так же, если id-выражение в выражении доступа к члену класса, для которого тип выражения объекта является текущим экземпляром, не ссылается на член текущего экземпляра или член неизвестной специализации, программа является некорректной даже если шаблон, содержащий выражение доступа к элементу, не создан; Диагностика не требуется.
Опять же, для этого кода не требуется никакой диагностики (и GCC фактически не дает его). Id-выражение a
в выражении доступа к члену this->a
зависел в C++03, потому что правила в этом стандарте были не такими сложными и отлаженными, как в C++11. На мгновение представим, что C++03 decltype
и конечные типы возврата. Что бы это значило?
- Поиск был бы отложен до момента создания экземпляра, потому что
this->a
будет зависеть - Поиск в экземпляре, скажем,
S<SomeClass>
потерпит неудачу, потому чтоthis->a
не будет найден во время создания экземпляра (как мы уже говорили, конечные типы возврата не видят члены, объявленные позже).
Следовательно, раннее отклонение этого кода на C++11 хорошо и полезно.
Тело функции-члена компилируется, как если бы оно было определено после класса. Поэтому все, что объявлено в классе, находится в области действия в этот момент.
Однако объявление функции все еще находится внутри объявления класса и может видеть только имена, предшествующие ему.
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b)); // error - a is not visible here
A a;
};
template <typename A>
template <typename B>
auto S<A>::f(B b) ->
decltype(a.f(b))
{
return a.f(b); // a is visible here
}
Стандарт гласит (раздел 14.6.2.1):
Если для заданного набора аргументов шаблона создается специализация шаблона, которая ссылается на член текущего экземпляра с выражением доступа с квалифицированным идентификатором или членом класса, то имя в выражении доступа с квалифицированным идентификатором или членом класса будет посмотрел в контексте создания шаблона.
this->a
является выражением доступа члена класса, поэтому это правило применяется, и поиск происходит в момент его создания, где S<A>
завершено.
Наконец, это совсем не решает вашу проблему, потому что в разделе 5.1.1 говорится:
Если объявление объявляет функцию-член или шаблон функции-члена класса X, выражение
this
является значением типа "указатель наcv-qualifier-seqX
”Между необязательным cv-qualifier-seq и концом определения функции,члена-декларатора илидекларатора. Он не должен появляться перед необязательнымcv-qualifier-seq и не должен появляться в объявлении статической функции-члена (хотя ее тип и категория значений определены в статической функции-члене, поскольку они находятся в нестатической функции-члене),
Так что вы не можете использоватьthis->
здесь, так как он находится перед частьюcv-qualifier-seq объявления функции.
Подождите, нет, это не так! Раздел 8.4.1 говорит
Декларатор в определениифункции должен иметь вид
D1 (
parameter-declaration-clause
)
cv-qualifier-seq opt ref-qualifier opt exception-specification opt attribute-specifier-seq opt trailing-return-type opt