Шаблоны C++ скрывают родительских элементов

Обычно, когда 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":

Если реализацию трудно объяснить, это - (вероятно), плохая идея.

5
задан Elazar Leibovich 7 March 2010 в 19:39
поделиться

3 ответа

Вы также можете сделать

class B : public A<T> {
    int getA() {return this->a;}
};

Проблема в том, что член находится в базе, которая зависит от параметра шаблона. Обычный неквалифицированный поиск выполняется в точке определения, а не в точке создания экземпляра, и поэтому он не выполняет поиск зависимых баз.

6
ответ дан 18 December 2019 в 13:13
поделиться

Поскольку есть вопросы о том, как неквалифицированные имена могут быть зависимыми, или как неквалифицированный поиск имени может применяться к зависимым именам:


Анализ следа: определение способа анализа оператора

Если в шаблоне встречается зависимое имя , всегда предполагается , а не для присвоения имени типу, , если только поиск по имени не обнаружит, что это тип, или мы добавим к имени 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 :: c как неквалифицированное имя) и сравните два имени, если они найдены, обозначают ли они один и тот же тип. Если мы не игнорируем зависимый базовый класс A во время поиска в области действия полного выражения, мы найдем foo в двух базовых классах , поэтому возникает двусмысленность. Игнорирование A будет означать, что мы найдем имя один раз, когда ищем в p -> , и в другой раз, когда ищем в TypeNameSugar .

6
ответ дан 18 December 2019 в 13:13
поделиться

Это распространенная проблема, однако ее вполне можно обойти, будь то для функций, типов или атрибутов.

Проблема возникает при реализации двухфазной оценки шаблонных классов и функций. Обычно стандарт требует, чтобы шаблоны оценивались два раза:

  • Первый раз при встрече, чтобы убедиться, что они правильно сформированы
  • Второй раз при внедрении, чтобы фактически произвести код для заданных типов

Во время первой оценки параметры шаблона неизвестны, поэтому невозможно сказать, каким будет базовый класс... и, в частности, содержит ли он член 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, делает вещи понятными для тех, кто пробирается через код.

2
ответ дан 18 December 2019 в 13:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: