提问人:pah 提问时间:5/22/2019 最后编辑:pah 更新时间:5/22/2019 访问量:145
为什么某些编译器上的 public 重载与 private using 指令冲突?
Why does public overload conflict with private using directive on some compilers?
问:
我在我的一个项目中遇到了以下情况,其中基类有一个函数模板,该模板被派生类模板中的非模板化函数隐藏。在类层次结构的更深处,非模板化函数通过 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>;
Godbolt: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
从中删除 private 会导致有关在基类中隐藏函数的警告。因此,不幸的是,这也不是一个理想的选择。using Base::get
Derived<T>
如果没有 ,则必须在 内限定调用,否则它就不是依赖名称。using Derived<T>::get
get
MoreDerived
有什么想法,我在这里做错了什么?
答:
我相信这里适用的是 [namespace.udecl]/17:
在不命名构造函数的 using 声明器中,引入的声明集的所有成员都应可访问。在命名构造函数的 using 声明器中,不执行访问检查。具体而言,如果派生类使用 using 声明符访问基类的成员,则成员名称应是可访问的。如果名称是重载成员函数的名称,则所有命名的函数都应可访问。[...]
(强调我的)与 [namespace.udecl]/19 结合使用:
由 using 声明创建的同义词具有成员声明的通常可访问性。[...]
using 声明 in 创建一个同义词,该同义词本身是由成员函数和成员函数模板组成的重载集的同义词。后者在 using 声明时不可访问(因为它在 中是私有的)。因此,GCC 和 Clang 是正确的,此代码不应编译。例如,将 using 声明从私有部分移入公共部分MoreDerived
Derived::get
Derived::get
Base::get
MoreDerived
Derived
Derived
template<typename T> class Derived : public Base
{
public:
using Base::get;
const T& get() const;
};
解决了问题...
评论
Base::get
const int& get() const;
Base::get
Derived::get
get<T>
get
Michael Kenzel 已经很好地解释了为什么你的代码会失败。
[...]但是使 get 无法编译 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>(); }
};
可能更好的选择是简单地使模板 getter 受到保护。
如果这对您来说可行,私人继承也应该解决这个问题;如果没有,另一个选项可能是将模板 getter 移动到一个新的、单独的基类,然后该基类将被私下继承。Base
这两种变体都会阻止
Derived<int> d;
static_cast<Base>(d).get<double>();
同样,如果这无论如何都毫无意义。
评论
Derived<T>::get
Base::get<T>
get<T>
using
MoreDerived
MoreDerived
删除
模板变体。
delete
using
MoreDerived
Base
评论
Derived<int> d; d.get<double>(); /* won't compile, but need: */ static_cast<Base>(d).get<double>();
get<U>
Derived<T>
const
using
const
get
Base
Base