В одном из своих проектов я столкнулся со следующей ситуацией, когда в базовом классе есть шаблон функции, который скрыт не шаблонной функцией в шаблоне производного класса. Ниже по иерархии классов функция без шаблона явно переносит функцию в область видимости с помощью директивы 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
, иначе это не будет зависимым именем.
Любые идеи, что я делаю неправильно здесь?
Я понимаю вашу озабоченность, но невозможность компиляции get<U>
для Derived<T>
на самом деле является особенностью исходного кода.
Является ли ваш вопрос «почему он разрешается к перегрузке, отличной от const
», или это «почему объявление using
тянет частные объявления»?
@zneak: я не вижу никаких не-const
перегрузок get
? Так что скорее последнее. :-)
Требуется ли публичное наследование от Base
? Частное наследование должно решить проблему. Если Base
потребности будет наследоваться публично, но геттер шаблона все равно должен быть скрыт, вы можете просто сделать его защищенным — или вы можете переместить его в базовый класс еще один, который будет наследоваться приватно.
Я считаю, что здесь применимо [пространство имен.udecl]/17:
In a using-declarator that does not name a constructor, all members of the set of introduced declarations shall be accessible. In a using-declarator that names a constructor, no access check is performed. In particular, if a derived class uses a using-declarator to access a member of a base class, the member name shall be accessible. If the name is that of an overloaded member function, then all functions named shall be accessible. […]
(выделено мной) в сочетании с [пространство имен.udecl]/19:
A synonym created by a using-declaration has the usual accessibility for a member-declaration. […]
Объявление using в 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;
};
решает вопрос…
Не уверен, что это все — код прекрасно компилируется, если превратить Base::get
в обычную функцию-член (const int& get() const;
).
@lubgr Я бы сказал, что когда вы превращаете Base::get
в обычную функцию-член, то Derived::get
(которая также является обычной функцией-членом) скроет ее, и, таким образом, проблема исчезнет…
@michael-kenzel Спасибо за ответ! Ссылка на стандарт очень помогает. Так вы говорите, что шаблон функции get<T>
не скрыт get
?
@тьфу Точно. Обычная функция-член не может скрыть шаблон функции-члена. Функции и шаблоны функций — это два разных вида вещей. Интуитивно представьте, что шаблон функции-члена, по сути, сам определяет весь набор потенциальных функций, поэтому одна функция не может скрыть весь этот набор…
Майкл Кензел уже хорошо объяснил почему ваш код не удался.
[...] but making get fail to compile for Derived is actually a feature of the original code
Хотя я не могу поощрять такой шаблон, поскольку вы нарушаете отношение «является собой», следующее может помочь вам:
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>();
также, если это все равно бессмысленно.
В моем случае Derived<T>::get
является оптимизацией и не делегирует Base::get<T>
. Вы мог вызываете get<T>
через базовый указатель, что даст вам более медленную реализацию (и вернет то же значение в конце). Двигаясь вперед, я думаю, что пропущу using
в MoreDerived
и вместо этого уточню вызовы внутри MoreDerived
.
@pah Вызов базового варианта - это просто пример реализации, конечно, вы можете заменить его на Любые другим ... Интересная часть о том, что delete
инг является вариантом шаблона.
Да, явный delete
варианта шаблона был бы альтернативой пропуску using
из MoreDerived
, спасибо.
@pah Не за что - в любом случае, подумайте о лучших альтернативах. Вам В самом деле нужно, чтобы геттер шаблона был общедоступным в Base
?
На самом деле никогда не бывает хорошей идеей делать открытые члены базовых классов закрытыми в производных классах; вы нарушаете отношение «является». А как же:
Derived<int> d; d.get<double>(); /* won't compile, but need: */ static_cast<Base>(d).get<double>();