Обычно, когда A
наследовался B
, все члены A
автоматически видимы к B
функции, например
class A {
protected:
int a;
};
class B : public A {
int getA() {return a;}
//no need to use A::a, it is automatically visible
};
Однако, когда я наследовался с шаблонами, этот код становится недопустимым (по крайней мере, в gcc
)
template<typename T>
class A {
protected:
int a;
};
template<typename T>
class B : public A<T> {
int getA() {return a;}
};
templt.cpp: In member function `int B<T>::getA()':
templt.cpp:9: error: `a' undeclared (first use this function)
templt.cpp:9: error: (Each undeclared identifier is reported only once for each
function it appears in.)
Я должен сделать один из
class B : public A<T> {
using B::a;
int getA() {return a;}
};
class B : public A<T> {
using A<T>::a;
int getA() {return a;}
};
class B : public A<T> {
int getA() {return B::a;}
};
и т.д. Как будто переменная a
скрыт другой переменной B
, в следующем случае:
class HiddenByOverload {void hidden(){}}
class HidesByOverload : public HiddenByOverload {
void hidden(int i) {} //different signature, now `hidden` is hidden
void usehidden() {
HiddenByOverload::hidden(); // I must expose it explicitly
}
}
Почему это так? Там какие-либо другие пути состоят в том, чтобы препятствовать тому, чтобы C++ скрыл переменные родительского шаблонного класса?
Править: спасибо за всех для захватывающего обсуждения. Я должен признать, что не следовал за аргументом, который заключил абзацы в кавычки из стандарта C++. Мне трудно следовать за ним, не читая фактический источник.
Лучшая вещь, которую я могу сделать для суммирования обсуждения, заключает короткую строку в кавычки из "Дзэн Python":
Если реализацию трудно объяснить, это - (вероятно), плохая идея.
Вы также можете сделать
class B : public A<T> {
int getA() {return this->a;}
};
Проблема в том, что член находится в базе, которая зависит от параметра шаблона. Обычный неквалифицированный поиск выполняется в точке определения, а не в точке создания экземпляра, и поэтому он не выполняет поиск зависимых баз.
Поскольку есть вопросы о том, как неквалифицированные имена могут быть зависимыми, или как неквалифицированный поиск имени может применяться к зависимым именам:
Если в шаблоне встречается зависимое имя , всегда предполагается , а не для присвоения имени типу, , если только поиск по имени не обнаружит, что это тип, или мы добавим к имени typename
:
template<typename T>
void f() {
T f0; // T is a template type parameter => type
T *f1;
typename T::name g1; // "name" is assumed to be a type.
T::name g0; // "name" cannot be looked up here => non-type
}
Этот поиск имени, чтобы определить, является ли оно типом, всегда выполняется в точке определения шаблона для всех зависимых имен: он направляет следующий синтаксический анализ в определенном направлении. Во втором операторе мы проанализируем T * f1
как объявление указателя, но не как умножение. В последнем утверждении мы предположили во время предварительного анализа неоднозначности, что T :: name
не является типом, и попытаемся проанализировать его как выражение. Это не удастся, потому что мы будем ожидать точки с запятой или некоторого оператора после T :: name
. Этот поиск, независимо от того, является ли имя типом, не влияет на значение имени на более поздних этапах: он еще не связывает имя с каким-либо объявлением.
После того, как мы определили, какие имена являются типами, а какие нет, мы фактически проанализируем шаблон.Имена, которые не являются зависимыми, то есть те, которые не просматриваются в области, которая является зависимой или которые явно не сделаны зависимыми другими правилами, ищутся в той точке, где они используются в шаблоне, и их значение не зависит от каких-либо деклараций, видимых при создании экземпляра.
Имена, которые являются зависимыми, ищутся при создании экземпляра, как в определении шаблона, в котором они используются, так и в том, где создается их шаблон. Это верно также для неквалифицированных имен, которые являются зависимыми:
template<typename T>
struct Bar {
void bar() { foo(T()); }
};
namespace A {
struct Baz { };
void foo(Baz); // found!
}
int main() { Bar<A::Baz> b; b.bar(); }
unqualified foo
становится зависимым от Стандарта, потому что аргумент T ()
зависит от типа. При создании экземпляра мы будем искать функции с именем foo
, используя неквалифицированный поиск имени по определению шаблона и используя поиск, зависящий от аргументов (то есть, грубо говоря, в пространстве имен T
) вокруг обоих определение шаблона и точка, в которой мы его создаем (после main
). Тогда поиск, зависящий от аргумента, найдет foo
.
Если Bar
теперь имеет зависимый базовый класс, неквалифицированный поиск должен игнорировать этот зависимый базовый класс:
template<typename T>
struct HasFoo { };
template<typename T>
struct Bar : HasFoo<T> {
void bar() { foo(T()); }
};
namespace A {
struct Baz { };
void foo(Baz); // found!
}
template<>
struct HasFoo<A::Baz> {
void foo();
};
int main() { Bar<A::Baz> b; b.bar(); }
Это все равно должно найти A :: foo
, несмотря на то, что неквалифицированный поиск по имени обнаружил бы функцию-член класса, если бы это было сделано (ADL не найдет дополнительных функций, если была найдена функция-член класса). Но неквалифицированный поиск имени не найдет эту функцию, потому что он является членом зависимого базового класса, и они игнорируются при поиске неквалифицированного имени.Другой интересный случай:
template<typename>
struct A {
typedef int foo;
operator int() {
return 0;
}
};
// makes sure applicable name-lookup
// classifies "foo" as a type (so parsing will work).
struct TypeNameSugar {
typedef int foo;
};
template<typename T>
struct C : A<T>, TypeNameSugar {
void c() {
A<T> *p = this;
int i = p->operator foo();
}
};
int main() {
C<void>().c();
}
Стандарт утверждает, что во время поиска foo
в operator foo
name мы будем искать независимо в обеих областях p ->
] (который находится в области действия класса A
) и области, в которой появляется полное выражение (которая является областью C
как неквалифицированное имя) и сравните два имени, если они найдены, обозначают ли они один и тот же тип. Если мы не игнорируем зависимый базовый класс A
во время поиска в области действия полного выражения, мы найдем foo
в двух базовых классах , поэтому возникает двусмысленность. Игнорирование A
будет означать, что мы найдем имя один раз, когда ищем в p ->
, и в другой раз, когда ищем в TypeNameSugar
.
Это распространенная проблема, однако ее вполне можно обойти, будь то для функций, типов или атрибутов.
Проблема возникает при реализации двухфазной оценки шаблонных классов и функций. Обычно стандарт требует, чтобы шаблоны оценивались два раза:
Во время первой оценки параметры шаблона неизвестны, поэтому невозможно сказать, каким будет базовый класс... и, в частности, содержит ли он член a
. Любой символ, который не зависит от одного из параметров шаблона, как ожидается, будет явно определен и будет проверяться.
Явно определив область видимости, вы отложите проверку до второй оценки, сделав символ зависимым от параметров шаблона.
Используя Boost
в качестве вдохновения:
template <class A1, class A2, class A3>
class MyClass: public Base<A1,A2,A3>
{
public:
typedef Base<A1,A2,A3> base_;
void foo()
{
// Accessing type
bar_type x; // ERROR: Not dependent on template parameter
typename base_::bar_type x;
// Accessing method
bar(); // ERROR: Not dependent on template parameter
this->bar();
base_::bar();
// Accessing attribute
mBar; // ERROR: Not dependent on template parameter
this->mBar;
base_::mBar;
};
}; // class MyClass
Мне нравится идиома Boost
определения base_
внутреннего типизатора. Во-первых, это, конечно, помогает определить конструкторы, а во-вторых, явная квалификация того, что происходит от класса Base, делает вещи понятными для тех, кто пробирается через код.