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

时间:2019-05-22 07:47:03

标签: c++ inheritance using-directives

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

using Base::get中删除专用Derived<T>会导致警告,提示您从基类中隐藏函数。因此,不幸的是,这也不是理想的选择。

在没有using Derived<T>::get的情况下,必须在get内限定对MoreDerived的调用,因为否则它将不是从属名称。

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

2 个答案:

答案 0 :(得分:4)

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

  

在未命名构造函数的 using-declarator 中,应可以访问引入的声明集中的所有成员。 using-declarator < / em>命名构造函数,不执行访问检查。特别是,如果派生类使用 using-declarator 访问基类的成员,则该成员名称应可访问。 如果名称是重载成员函数的名称,则所有命名的函数均应可访问。 […]

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

  

using-declaration 创建的同义词通常具有 member-declaration 的可访问性。 […]

MoreDerived中的using声明创建Derived::get的同义词,其本身是由成员函数Derived::get和成员函数模板Base::get组成的重载集的同义词。后者在MoreDerived中的using声明处无法访问(因为它在Derived中是私有的)。因此,GCC和Clang是正确的,不应编译此代码。将Derived中的using声明从私有部分移到公共部分,例如

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

解决了问题...

答案 1 :(得分:0)

Michael Kenzel已经很好地解释了why your code did fail

  

[...]但使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受保护。

如果您认为可行,则Base的私有继承也可以解决此问题;如果不是这样,另一种选择可能是将模板获取器移动到一个新的,独立的基类,然后该基类将被私有继承。

两个变体都可以防止

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

如果这仍然没有意义的话。