Ответ 1
template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};
Это работает, потому что в концевом возвращаемом типе видны члены окружающего класса. Не все члены, а только те члены, которые были объявлены до него (в концевом типе возврата класс не считается полным, в отличие от тел функции). Итак, что здесь делается:
- Как мы находимся в шаблоне, выполняется поиск, зависящий от того, зависит ли
a
или нет. Посколькуa
был объявлен доf
,a
обнаруживается, что он относится к члену класса. -
По правилам шаблона в С++ обнаруживается, что
a
относится к члену текущего экземпляра, поскольку он является членом экземпляров окружающего шаблона. В С++ это понятие используется главным образом для определения того, зависят ли имена: если имя известно как относящееся к окружающим членам шаблона, необязательно нужно искать при создании экземпляра, поскольку компилятор уже знает код шаблона (который используется в качестве основы для экземпляра класса, созданного из него!). Рассмотрим:template<typename T> struct A { typedef int type; void f() { type x; A<T>::type y; } };
В С++ 03 вторая строка, объявляющая y
, будет ошибкой, потому что A<T>::type
является зависимым именем и ему нужно typename
перед ним. Только первая линия была принята. В С++ 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
. В С++, помимо понятия "член текущей экземпляра", существует другое понятие:
-
член неизвестной специализации. Это понятие используется для обозначения случая, когда имя может ссылаться на член класса, который зависит от параметров шаблона. Если бы мы получили доступ к
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
, которые зависят от параметров шаблона), имя также не является членом неизвестной специализации,
Снова С++ не имеет правил, чтобы сделать this->a
зависимым. Однако он использует this->
, поэтому имя должно ссылаться на некоторый член S
при его создании! Поэтому в С++ Standard говорится
Аналогично, если выражение id в выражении доступа к члену класса, для которого тип выражения объекта является текущим экземпляром, не относится к члену текущего экземпляра или к члену неизвестной специализации, программа болен -форменное, даже если шаблон, содержащий выражение доступа к члену, не создается; не требуется диагностика.
Опять же, для этого кода не требуется диагностика (и GCC на самом деле его не дает). Идентификатор-выражение a
в выражении доступа к члену this->a
зависел в С++ 03, потому что правила в этом стандарте не были так подробно разработаны и точно настроены, как в С++ 11. На мгновение предположим, что у С++ 03 были decltype
и возвращающие возвращаемые типы. Что это значит?
- Поиск будет отложен до создания экземпляра, потому что
this->a
будет зависимым - Поиск в экземпляре, скажем,
S<SomeClass>
завершится неудачно, потому чтоthis->a
не будет найден во время создания экземпляра (как мы уже говорили, возвращающие возвращаемые типы не видят участников, объявленных позже).
Следовательно, раннее отклонение этого кода на С++ 11 является хорошим и полезным.