Подтвердить что ты не робот

Почему стандарт С++ указывает, что неквалифицированные имена в шаблоне не зависят?

Почему стандарт С++ указывает, что неквалифицированные имена в шаблоне не зависят?

например.

template<typename T>
class Base
{
public:
    T x;
};

template<typename T>
class C : public Base<T>
{
public:
    bool m() { return x == 0; } // Error: undeclared identifier 'x'
};

Цитата из принятого ответа на SO question о том, как преодолеть ограничение:

В стандарте указано, что неквалифицированные имена в шаблоне не зависящий от него, и его нужно искать, когда шаблон определен. определение зависимого базового класса в это время неизвестно (могут существовать специализированные шаблоны базового класса), поэтому неквалифицированные имена не могут быть разрешены.

Однако в цитированных и других ответах не указывается, почему это указано в стандарте. В чем причина этого ограничения?

4b9b3361

Ответ 1

Это не совсем то, что на самом деле говорит стандарт. Фактически это говорит о том, что зависимые базовые классы не проверяются при неквалифицированном поиске имени. Несомненное имя, конечно, может зависеть от правильного контекста - как работает ADL в шаблонах: заданный параметр типа шаблона T, foo in foo(T()) является зависимым именем.

В любом случае причина, по которой он не может выполнить такой поиск, прост: при определении времени шаблона вы не представляете, как будет выглядеть зависимый базовый класс, благодаря специализациям, которые могут появиться позже, так что вы не можете сделать никакого значимого поиска. Каждый орфографический идентификатор в шаблоне с зависимым базовым классом не может быть диагностирован до тех пор, пока он не будет создан, если когда-либо. И так как каждый неквалифицированный идентификатор мог быть из зависимого базового класса, вам понадобится какой-то способ ответить "это тип?". и "это шаблон?", так как ответы на эти вопросы влияют на синтаксический анализ. Это означает что-то вроде страшных typename и template ключевых слов, но в гораздо большем количестве мест.

Ответ 2

Я думаю, что это вопрос согласованности. Рассмотрим это, немного измененный пример:

заголовочный файл:

template<typename T>
class Base
{
public:
    T x;
};

extern int x;

template<typename T>
class C : public Base<T>
{
public:
    bool m() { return x == 0; }
};

и исходный файл:

template<>
class Base<int> // but could be anything
{
public:
    // no x here
};

int main()
{
  C<char> c1;
  c1.m(); // may select ::x or Base<char>::x
  C<int> c2;
  c2.m(); // has only one choice: ::x
  return(0);
}

Формулировка в стандарте гарантирует, что компилятор будет либо выходить из строя, либо выбирать любой символ, который он видит в точке определения шаблона. Если компилятор должен отложить разрешение имени для создания шаблона, он может затем выбрать различные объекты, видимые в этой точке и которые могут удивить разработчика.

Если разработчик WANTS получает доступ к зависимому имени, он должен указать это явно, и он не должен быть затем снят с охраны.

Также обратите внимание, что если x не был доступен в точке определения шаблона, следующий код сломался бы (неожиданно). Одно правило определения:

one.cpp

#include <template_header>

namespace {
int x;
}

void f1()
{
  C<int> c1;
}

two.cpp

#include <template_header>

namespace {
char x;
}

void f2()
{
  C<int> c2;
}

Можно было бы ожидать, что переменные c1 и c2 одного типа и могут, например, безопасно передать две функции, принимающие параметр C<int> const &, но в основном две переменные имеют одно и то же имя типа, но реализации отличаются.

Ответ 3

Совместим со стандартом или нет, в Visual Studio 2015 - это совсем не проблема. Более того, несмотря на комментарий от Tomek, компилятор принимает правильный x

static int x = 42;

template<typename T>
class Base {
public:
    T x;
    Base() { x = 43; }
};

template<>
class Base<int> { };

template<typename T>
class C :public Base<T>
{
public:
    int m() { return x; }
};

void main() {
    C<int> cint;
    C<char> cchar;

    std::cout << "cint: " << cint.m() << std::endl;
    std::cout << "cchar: " << cchar.m() << std::endl;
}

// Output is:
// cint: 42
// cchar: 43