为什么某些编译器上的 public 重载与 private using 指令冲突?

Why does public overload conflict with private using directive on some compilers?

提问人:pah 提问时间:5/22/2019 最后编辑:pah 更新时间:5/22/2019 访问量:145

问:

我在我的一个项目中遇到了以下情况,其中基类有一个函数模板,该模板被派生类模板中的非模板化函数隐藏。在类层次结构的更深处,非模板化函数通过 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::getDerived<T>

如果没有 ,则必须在 内限定调用,否则它就不是依赖名称。using Derived<T>::getgetMoreDerived

有什么想法,我在这里做错了什么?

C++ 继承 using 指令

评论

2赞 Aconcagua 5/22/2019
实际上,在派生的基类中将基类的公共成员私有化从来都不是一个好主意;您违反了“是 A”关系船。怎么样:Derived<int> d; d.get<double>(); /* won't compile, but need: */ static_cast<Base>(d).get<double>();
0赞 pah 5/22/2019
我理解您的担忧,但编译失败实际上是原始代码的一个功能。get<U>Derived<T>
0赞 zneak 5/22/2019
您的问题是“为什么它要解决非过载”,还是“为什么声明会拉入私人声明”?constusing
0赞 pah 5/22/2019
@zneak:我没有看到任何非重载的?所以是后者。:-)constget
1赞 Aconcagua 5/22/2019
是否要求从公开继承?私人继承应该可以解决这个问题。如果需要公开继承,但模板 getter 无论如何都应该被隐藏,你可以简单地让它受到保护——或者你可以把它移动到另一个将私下继承的基类。BaseBase

答:

4赞 Michael Kenzel 5/22/2019 #1

我相信这里适用的是 [namespace.udecl]/17

在不命名构造函数的 using 声明器中,引入的声明集的所有成员都应可访问。在命名构造函数的 using 声明器中,不执行访问检查。具体而言,如果派生类使用 using 声明符访问基类的成员,则成员名称应是可访问的。如果名称是重载成员函数的名称,则所有命名的函数都应可访问。[...]

(强调我的)与 [namespace.udecl]/19 结合使用:

由 using 声明创建的同义词具有成员声明的通常可访问性。[...]

using 声明 in 创建一个同义词,该同义词本身是由成员函数和成员函数模板组成的重载集的同义词。后者在 using 声明时不可访问(因为它在 中是私有的)。因此,GCC 和 Clang 是正确的,此代码不应编译。例如,将 using 声明从私有部分移入公共部分MoreDerivedDerived::getDerived::getBase::getMoreDerivedDerivedDerived

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

解决了问题...

评论

0赞 lubgr 5/22/2019
不确定这是整个故事 - 如果你变成一个普通的成员函数(),代码编译得很好。Base::getconst int& get() const;
2赞 Michael Kenzel 5/22/2019
@lubgr我想说的是,当你变成一个普通的成员函数时,那么(这也是一个普通的成员函数)会隐藏它,因此,问题就消失了......Base::getDerived::get
0赞 pah 5/22/2019
@michael-kenzel 感谢您的回复!对标准的引用有很大帮助。所以你是说函数模板没有被隐藏?get<T>get
0赞 Michael Kenzel 5/22/2019
@pah 没错。普通成员函数不能隐藏成员函数模板。函数和函数模板是两种不同的东西。直观地说,假设成员函数模板本质上定义了一整套潜在函数本身,因此仅凭一个函数不可能隐藏整个函数集......
1赞 Aconcagua 5/22/2019 #2

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>();

同样,如果这无论如何都毫无意义。

评论

0赞 pah 5/22/2019
就我而言,这是一个优化,不会委托给 .您可以通过基指针调用,这将为您提供较慢的实现(并在最后返回相同的值)。展望未来,我想我将跳过 in 并限定内部的调用。Derived<T>::getBase::get<T>get<T>usingMoreDerivedMoreDerived
0赞 Aconcagua 5/22/2019
@pah 调用基本变体只是一个实现示例,当然您可以用任何其他变体替换它......有趣的部分是删除模板变体。
0赞 pah 5/22/2019
是的,模板变体的显式将是跳过 from 的替代方法,谢谢。deleteusingMoreDerived
0赞 Aconcagua 5/22/2019
@pah 不客气——无论如何,想想更好的选择。你真的需要模板 getter 公开吗?Base