Если бы приведенные ниже классы не были шаблонами, я мог бы просто иметь их x
в derived
классе. Тем не менее, с кодом ниже, я должен использовать this->x
. Зачем?
template <typename T>
class base {
protected:
int x;
};
template <typename T>
class derived : public base<T> {
public:
int f() { return this->x; }
};
int main() {
derived<int> d;
d.f();
return 0;
}
x
withthis->
, а именно: 1) Использовать префиксbase<T>::x
, 2) Добавить операторusing base<T>::x
, 3) Использовать глобальный переключатель компилятора, который включает разрешающий режим. Плюсы и минусы этих решений описаны в stackoverflow.com/questions/50321788/…Ответы:
Краткий ответ: чтобы создать
x
зависимое имя, чтобы поиск откладывался до тех пор, пока не будет известен параметр шаблона.Длинный ответ: когда компилятор видит шаблон, он должен немедленно выполнить определенные проверки, не видя параметра шаблона. Другие откладываются до тех пор, пока параметр не станет известен. Это называется двухфазной компиляцией, и MSVC этого не делает, но это требуется стандартом и реализуется другими основными компиляторами. Если вам нравится, компилятор должен скомпилировать шаблон, как только он его увидит (для какого-то внутреннего представления дерева разбора), и отложить компиляцию до более поздней версии.
Проверки, которые выполняются над самим шаблоном, а не над его конкретными экземплярами, требуют, чтобы компилятор мог разрешать грамматику кода в шаблоне.
В C ++ (и C) для разрешения грамматики кода иногда необходимо знать, является ли что-то типом или нет. Например:
если A является типом, который объявляет указатель (без эффекта, кроме как для затенения глобального
x
). Если A - это объект, это умножение (и запрет на перегрузку некоторых операторов недопустимо, присваивая значение r). Если это не так, эта ошибка должна быть диагностирована на этапе 1 , она определяется стандартом как ошибка в шаблоне , а не в какой-то конкретной ее реализации. Даже если шаблон никогда не создается, если A является a,int
то приведенный выше код является некорректным и должен быть диагностирован, как если бы этоfoo
был не шаблон вообще, а простая функция.Теперь, согласно стандарту, имена, которые не зависят от параметров шаблона, должны быть разрешены на этапе 1.
A
Здесь это не зависимое имя, оно относится к одному и тому же, независимо от типаT
. Поэтому его необходимо определить до того, как шаблон будет определен, чтобы его можно было найти и проверить на этапе 1.T::A
было бы имя, которое зависит от T. Мы не можем знать, на этапе 1, является ли это тип или нет. Тип, который в конечном итоге будет использоваться какT
в экземпляре, вполне вероятно, еще даже не определен, и даже если бы это было так, мы не знаем, какой тип (типы) будет использоваться в качестве параметра шаблона. Но мы должны разрешить грамматику, чтобы выполнить наши драгоценные проверки фазы 1 для плохо сформированных шаблонов. Таким образом, в стандарте есть правило для зависимых имен - компилятор должен предполагать, что они не являются типами, если только он не квалифицированtypename
для указания того, что они являются типами или используются в определенных однозначных контекстах. Например , вtemplate <typename T> struct Foo : T::A {};
,T::A
используется в качестве базового класса и , следовательно , однозначно тип. ЕслиFoo
создается экземпляр с некоторым типом, который имеет член данныхA
вместо вложенного типа A это ошибка в коде, выполняющем создание экземпляра (фаза 2), а не ошибка в шаблоне (фаза 1).Но как насчет шаблона класса с зависимым базовым классом?
Является ли зависимое имя или нет? С базовыми классами любое имя может появиться в базовом классе. Таким образом, мы можем сказать, что A является зависимым именем, и рассматривать его как нетиповое. Это может иметь нежелательный эффект, поскольку каждое имя в Foo является зависимым, и, следовательно, каждый тип, используемый в Foo (кроме встроенных типов), должен быть квалифицирован. Внутри Foo вы должны написать:
потому
std::string
что будет зависимым именем, и, следовательно, предполагается, что это не тип, если не указано иное. Ой!Вторая проблема с разрешением вашего предпочтительного кода (
return x;
) состоит в том, что даже еслиBar
он определен ранееFoo
иx
не является членом в этом определении, кто-то может позже определить специализациюBar
для некоторого типаBaz
, такогоBar<Baz>
как элемент данныхx
, и затем создать экземплярFoo<Baz>
, Таким образом, в этом случае ваш шаблон будет возвращать элемент данных, а не глобальныйx
. Или, наоборот, если определение базового шаблонаBar
имело местоx
, они могли бы определить специализацию без него, и ваш шаблон мог бы искать глобальноеx
возвращениеFoo<Baz>
. Я думаю, что это было так же удивительно и печально, как и ваша проблема, но это молча удивительно, в отличие от неожиданной ошибки.Чтобы избежать этих проблем, действующий стандарт гласит, что зависимые базовые классы шаблонов классов просто не рассматриваются для поиска без явного запроса. Это мешает всему быть зависимым только потому, что его можно найти в зависимой базе. Это также имеет нежелательный эффект, который вы видите - вы должны квалифицировать материал из базового класса, или он не найден. Есть три распространенных способа сделать
A
зависимость:using Bar<T>::A;
в классе -A
теперь относится к чему-то вBar<T>
, следовательно, зависимым.Bar<T>::A *x = 0;
в точке использования - опять же,A
безусловно, вBar<T>
. Это умножение, поскольку оноtypename
не использовалось, поэтому, возможно, это плохой пример, но нам придется подождать до создания экземпляра, чтобы выяснить,operator*(Bar<T>::A, x)
возвращает ли значение r. Кто знает, может быть, это так ...this->A;
в точке использования -A
член, поэтому, если он не включенFoo
, он должен быть в базовом классе, опять же, в стандарте говорится, что это делает его зависимым.Двухфазная компиляция сложна и трудна, и вводит некоторые неожиданные требования для дополнительной словесности в вашем коде. Но, скорее, как и демократия, это, пожалуй, худший из возможных способов, помимо всех остальных.
Вы можете обоснованно утверждать, что в вашем примере
return x;
не имеет смысла, еслиx
это вложенный тип в базовом классе, поэтому язык должен (а) сказать, что это зависимое имя и (2) обрабатывать его как не тип, и ваш код будет работать безthis->
. В какой-то степени вы стали жертвой сопутствующего ущерба от решения проблемы, которая не применима в вашем случае, но все еще существует проблема, связанная с тем, что ваш базовый класс потенциально вводит имена под вами, которые являются теневыми глобалами, или не имеет имен, которые вы считали они имели, и вместо этого был найден глобальный.Можно также утверждать, что значение по умолчанию должно быть противоположным для зависимых имен (предполагается, что тип, если не указано иное, чтобы быть объектом), или что значение по умолчанию должно быть более контекстно-зависимым (в
std::string s = "";
,std::string
может быть прочитано как тип, так как ничто иное не делает грамматическим смысл хоть иstd::string *s = 0;
неоднозначный). Опять же, я не знаю, как были согласованы правила. Я предполагаю, что количество страниц текста, которые потребуются, сведено к минимуму с созданием множества конкретных правил, для которых контексты принимают тип, а какие нетипизируются.источник
-fpermissive
или похожи, да, это возможно. Я не знаю деталей того, как это реализовано, но компилятор должен отложить разрешениеx
до тех пор, пока не узнает фактический базовый класс tempateT
. Таким образом, в принципе в недопустимом режиме он может зафиксировать факт, что он отложил его, отложить его, выполнить поиск, как только он произойдетT
, и, когда поиск завершится успешно, выдать предложенный вами текст. Это было бы очень точным предложением, если бы оно было сделано только в тех случаях, когда оно работает: шансы, что пользователь имел в виду что-то другоеx
из еще одной области, довольно крошечные!(Оригинальный ответ от 10 января 2011 г.)
Я думаю, что нашел ответ: проблема GCC: использование члена базового класса, который зависит от аргумента шаблона . Ответ не является специфичным для gcc.
Обновление: в ответ на комментарий Микаэля , из проекта N3337 стандарта C ++ 11:
Является ли « потому что стандарт говорит так» , считается, что ответ, я не знаю. Теперь мы можем спросить, почему стандарт предписывает это, но, как отмечают превосходный ответ Стива Джессопа и другие, ответ на этот последний вопрос довольно длинный и спорный. К сожалению, когда речь идет о стандарте C ++, зачастую почти невозможно дать краткое и самостоятельное объяснение того, почему стандарт требует чего-то; это относится и к последнему вопросу.
источник
Это
x
скрыто во время наследования. Вы можете показать через:источник