Разница между GCC и Clang в поиске квалифицированных членов класса в шаблонах

Вопрос или проблема

Следующий код дает разные результаты при компиляции с использованием gcc 14.2 или clang 19.1:

namespace mylib {
    template <typename Type> void Function(const Type& t) {
        // Примечание: явное указание Type::
        t.Type::Foo();
    }
}

class Type {
    public:
        void Foo() const {
            std::cout << "Базовая функция";
        }
};

class DerivedType: public Type {
    public:
        void Foo() const {
            std::cout << "Производная функция";
        }
};

int main() {
    mylib::Function(DerivedType{});
}
  • в gcc 14.2: вызывается DerivedType::Foo().
  • в clang 19.1: вызывается Type::Foo().

Есть ли идеи о том, какое поведение следует считать правильным, если таковое существует?

Примечание: оба компилятора выдают одинаковый вывод, если class Type переименовать в нечто иное, кроме аргумента шаблона Function, в этом случае DerivedType::Foo также вызывается clang.

Это (в настоящее время открытая) ошибка CWG 1089.

Согласно текущим правилам, поиск Type выполняется при инстанцировании в контексте типа класса t сначала, и шаблонный параметр с тем же именем может быть найден только во время неявного поиска после этой первой попытки, если Type не найден в контексте класса.

Здесь Type может быть найден в области класса DerivedType типа t, указывая на базовый класс, и Type::Foo тогда будет ссылаться на член-функцию этого базового класса Type.

Таким образом, здесь Clang прав.

Ответ или решение

Вопрос о различиях в области видимости квалифицированных имен членов в шаблонах между компиляторами GCC и Clang касается сложных правил C++ и их интерпретации различными компиляторами. Рассмотрим ваш пример кода подробнее:

namespace mylib {
    template <typename Type> void Function(const Type& t) {
        // Обратите внимание: явная квалификация Type:: 
        t.Type::Foo();
    }
}

class Type {
    public:
        void Foo() const {
            std::cout << "Base function";
        }
};

class DerivedType: public Type {
    public:
        void Foo() const {
            std::cout << "Derived function";
        }
};

int main() {
    mylib::Function(DerivedType{});
}

Поведение компиляторов

При компиляции этого кода с использованием GCC 14.2, вызывается метод Foo у класса DerivedType. Однако при использовании Clang 19.1 вызывается метод Foo у базового класса Type. Это приводит к вопросу о корректности поведения этих компиляторов.

Правила поиска имени

Согласно текущим стандартам C++, при использовании квалифицированного имени происходит несколько этапов поиска:

  1. Первичный поиск: Во время инстанциации шаблона, компилятор ищет имя Type в контексте класса объекта t. Этот поиск включает в себя базовый класс, если типом t является производный класс. То есть, компилятор сначала проверяет, доступно ли имя Type в контексте DerivedType, что приводит к обнаружению Type.

  2. Несквозной поиск: Если имя Type не найдено в контексте класса, компилятор выполняет несквозной поиск, в котором также могут быть учтены шаблонные параметры. Однако, в данном примере имя Type успешно обнаруживается в базовом классе, что должно приводить к ссылке на Type::Foo().

Вывод

На основании анализа правил C++ можно заключить, что поведение Clang является корректным. Поиск имени Type в контексте класса DerivedType приводит к тому, что компилятор видит член Type::Foo. Следовательно, вызов t.Type::Foo() будет ссылаться на метод базового класса Type, как это и происходит в Clang.

Также стоит подчеркнуть, что если имя класса Type будет изменено на что-то отличное от имени шаблонного параметра, поведение будет единообразным для обоих компиляторов: DerivedType::Foo() будет вызванным.

Заключение

Различия в поведении между GCC и Clang в данном примере подчеркивают важность понимания правил поиска имен в C++. На данный момент ясным представляется, что Clang следует существующим стандартам, а поведение GCC может быть пересмотрено в будущем. Это также поднимает вопрос о необходимости разъяснения и, возможно, изменения стандартов для обеспечения единообразия между компиляторами. Факт открытия CWG-дела № 1089 подтверждает, что это общепринятая проблема в сообществе разработчиков языков программирования.

Оцените материал
Добавить комментарий

Капча загружается...