ошибка Visual studio или изменен стандарт c ?

#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() он виртуальный, так как он не будет отправляться виртуально. Вот почему ответ @Swift this->hello() является правильным.

4. @Ry-Fi В вопросе, он не виртуальный. Так что это сработает в данном конкретном случае.