#c #visual-studio-2017 #visual-studio-2019 #c 20
Вопрос:
#include <iostream>
template<typename T>
struct base {
void hello() {
}
};
template<typename T>
struct ttt : public base<ttt<T>> {
public:
ttt() {
hello();
}
};
int main() {
ttt<int> t();
return 0;
}
когда я использую c 17 или 11, этот код просто в порядке. но когда я установил стандарт c на 20, произошла ошибка C3816, в которой говорится, что не удается найти функцию hello, я не понимаю причины, по которой эта ошибка возникает до c 20.
Комментарии:
1. среда разработки : windows visual studio enterprise 2019 предварительный просмотр(16.10.0 предварительный просмотр 3.0)
2. Код не должен компилироваться. Принятие этого кода является ошибкой/несоответствием MSVC, которая теперь исправлена в разделе
/std:c latest
. Видишь docs.microsoft.com/en-us/cpp/build/reference/…3. спасибо за ваше объяснение, трудно смириться с тем, что мой код до сих пор был неправильным.
4. Этот код может компилироваться с включенным «разрешающим». docs.microsoft.com/en-us/cpp/build/reference/…
5. @degawong, кстати, даже решив это, у вас все равно будет самый неприятный синтаксический анализ, если вы попытаетесь
t.hello()
, потомуttt<int> t();
что он интерпретируется как объявление функции. Так и должно бытьttt<int> t{};
вместо этого.
Ответ №1:
В hello();
заявлении не зависит от аргумента шаблона, и нет такого имени в немедленной выдаче контекста (т. е. внутри ttt
или его не-шаблона базового класса, если они есть), так что имени будет сделано в контексте шаблона. И. Е. если вы имели глобальную функцию с именем hello
, он будет выбран вместо этого. Это диктуется всеми стандартами, по крайней мере, начиная с C 11. Стандартный способ сделать это — сделать значение prvalue зависимым от аргумента шаблона:
this->hello(); // this is dependant on T
Другим способом является использование полного имени с использованием base<ttt<T>>::
префикса, но это означает, что можно выбрать только эту версию функции. Если ttt
переопределит его, если дерево наследования расширится и произойдет еще одна перегрузкапереопределение hello
, если будет присутствовать множественное виртуальное наследование, такое полное имя может указывать на непреднамеренный элемент функции.
Ошибка в Visual Studio восходит, по крайней мере, к VS2010, я лично страдал от нее в течение многих лет, потому что разработчики писали код, который компилировался с использованием MSVC, но не компилировался gcc, и мне приходилось каждый раз объяснять им, что не так («Но он компилируется!»).
Комментарии:
1. Я бы назвал это неподтвержденным поведением, а не ошибкой. Я не понимаю, почему это не может быть стандартом для всех.
2. @RobertAndrzejuk вы, конечно, правы насчет того, как это назвать. Но будучи необъявленным, это приводит к появлению некоторых скрытых ошибок в скомпилированном коде, может возникнуть противоположная ситуация, когда мы на самом деле намерены вызвать глобальную или просто не шаблонную функцию, сослаться на глобальную переменную и т. Д. VS нарушал его, создавая неправильно работающий код. Это может привести к не поддающемуся отладке беспорядку в системах со сложной иерархией.
3. @RobertAndrzejuk: Стандартное поведение превосходно тем, что оно позволяет избежать вызова функции, которая выглядит как вызов свободной функции, на самом деле вызывающей функцию-член (или же изменения обычных правил затенения, если вы предпочитаете свободную функцию). Неспособность реализовать это, безусловно, была ошибкой (если она понятна, учитывая винтаж компилятора).
Ответ №2:
Это изменение настроек по умолчанию.
Начиная с версии 16.8 Visual Studio 2019, параметр /std:c последней версии неявно задает параметр /permissive -.
https://docs.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance?view=msvc-160
Чтобы исправить код, префикс имени функции с именем базового класса:
base<ttt<T>>::hello();
На вершине своего класса я хотел бы добавить :
using Base = base<ttt<T>>;
Тогда я смогу позвонить :
Base::hello();
Комментарии:
1. в некоторых случаях
base<ttt<T>>::hello();
может быть слишком специфичным, если эта функция была переопределена или защищена другой внутри цепочки наследования.2. Ключ состоит в том, чтобы использовать
::
, что делает его зависимым именем. Поэтому можно просто обратиться к текущему классу:ttt::hello();
.3. @rustyx, но
ttt::hello()
неверно, еслиhello()
он виртуальный, так как он не будет отправляться виртуально. Вот почему ответ @Swiftthis->hello()
является правильным.4. @Ry-Fi В вопросе, он не виртуальный. Так что это сработает в данном конкретном случае.