Почему публичная перегрузка конфликтует с приватной директивой using на некоторых компиляторах? - PullRequest
11 голосов
/ 22 мая 2019

Я сталкивался со следующей ситуацией в одном из моих проектов, где базовый класс имеет шаблон функции, который скрыт не шаблонной функцией в шаблоне производного класса. Далее по иерархии классов функция без шаблонов явно переносит функцию в область действия с помощью директивы using.

Вот упрощенный пример кода:

class Base
{
public:
  template<typename T> const T& get() const;
};

template<typename T> class Derived : public Base
{
private:
  using Base::get;
public:
  const T& get() const;
};

template<typename T> class MoreDerived : public Derived<T>
{
public:
  using Derived<T>::get; // <-- this causes the problem

  const T& call_get() {
    return get();
  }
};

template class MoreDerived<int>;

Годболт: https://godbolt.org/z/5MQ0VL

Приведенный выше код не работает в GCC и Clang с такими ошибками:

<source>:15:28: error: 'template<class T> const T& Base::get() const' is inaccessible within this context

   15 | template<typename T> class MoreDerived : public Derived<T>

MSVC и ICC принимают этот код без жалоб.

Мне интересно, почему компиляторы жалуются на Base::get<T> в то время как там есть публичная перегрузка Derived<T>::get доступна?

Удаление частного using Base::get из Derived<T> приводит к предупреждению о сокрытии функций от базового класса. Так что, к сожалению, это тоже не идеальный вариант.

Без using Derived<T>::get вызовы get должны быть квалифицированы в пределах MoreDerived, так как в противном случае это не было бы зависимое имя.

Есть идеи, что я тут не так делаю?

Ответы [ 2 ]

4 голосов
/ 22 мая 2019

Я считаю, что здесь применимо [namespace.udecl] / 17 :

В использующем-деклараторе , который не называетконструктор, все члены набора введенных объявлений должны быть доступны. В using-деклараторе , который называет конструктор, проверка доступа не выполняется.В частности, если производный класс использует using-декларатор для доступа к члену базового класса, имя члена должно быть доступно. Если имя совпадает с именем перегруженной функции-члена, тогда все названные функции должны быть доступны. […]

(выделено мной) в сочетании с [пространство имен.udecl] / 19 :

Синоним, созданный декларацией использования , имеет обычную доступность для декларации члена .[…]

Объявление использования в MoreDerived создает синоним для Derived::get, который сам является синонимом для набора перегрузки, состоящего из функции-члена Derived::get и шаблона функции-члена Base::get.Последний недоступен в момент объявления использования в MoreDerived (потому что он закрыт в Derived).Таким образом, GCC и Clang верны, этот код не должен компилироваться.Перемещение декларации использования в Derived из приватной в публичную часть, например

template<typename T> class Derived : public Base
{
public:
  using Base::get;
  const T& get() const;
};

, решает проблему…

1 голос
/ 22 мая 2019

Майкл Кензел уже хорошо объяснил , почему ваш код потерпел неудачу .

[...], но сбой при компиляции для Derived на самом делеособенность оригинального кода

Хотя я не могу поощрять такой шаблон, так как вы нарушаете отношение «является», следующее может помочь вам:

class Base
{
public:
  template<typename T>
  const T& get() const;
};

template<typename T> class Derived : public Base
{
public:
    template<typename U>
    U const& get() const = delete;
    T const& get() const { return Base::get<T>(); }
};

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

Частное наследование Base также должно решить эту проблему, если это возможно для вас;в противном случае другой вариант может переместить средство получения шаблона в новый отдельный базовый класс, который затем будет унаследован в частном порядке.

Оба варианта также предотвратят

Derived<int> d;
static_cast<Base>(d).get<double>();

, если этов любом случае бессмысленно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...