为什么 public 重载与某些编译器上的 private using 指令冲突?
Why does public overload conflict with private using directive on some compilers?
我在我的一个项目中遇到过以下情况,其中基础 class 有一个函数模板,它被派生的 class 模板中的非模板函数隐藏。在 class 层次结构的下方,非模板函数通过 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>
而 是 public 重载 Derived<T>::get
可用?
从 Derived<T>
中删除私有 using Base::get
会导致有关从基 class 中隐藏函数的警告。所以不幸的是,这也不是一个理想的选择。
如果没有 using Derived<T>::get
,对 get
的调用必须在 MoreDerived
内限定,否则它就不是从属名称。
任何想法,我在这里做错了什么?
我相信这里适用的是[namespace.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. […]
(强调我的)结合 [namespace.udecl]/19:
A synonym created by a using-declaration has the usual accessibility for a member-declaration. […]
MoreDerived
中的 using 声明为 Derived::get
创建了一个同义词,它本身就是由成员函数 Derived::get
和成员函数模板 [=14] 组成的重载集的同义词=].后者在 MoreDerived
中的 using 声明点不可访问(因为它在 Derived
中是私有的)。因此,GCC 和 Clang 是正确的,这段代码不应该编译。将 Derived
中的 using 声明从 private 移动到 public 部分,例如
template<typename T> class Derived : public Base
{
public:
using Base::get;
const T& get() const;
};
解决了问题…
Michael Kenzel already explained nicely .
[...] but making get fail to compile for Derived is actually a feature of the original code
尽管我不鼓励这种模式,因为您违反了 "is a" 关系,但以下内容可能对您有用:
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
的私有继承应该也能解决这个问题,如果这对你可行的话;如果没有,另一种选择可能是将模板 getter 移动到一个新的、单独的基础 class,然后它将被私下继承。
两种变体都会阻止
Derived<int> d;
static_cast<Base>(d).get<double>();
还有,如果这无论如何都没有意义的话。
我在我的一个项目中遇到过以下情况,其中基础 class 有一个函数模板,它被派生的 class 模板中的非模板函数隐藏。在 class 层次结构的下方,非模板函数通过 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>
而 是 public 重载 Derived<T>::get
可用?
从 Derived<T>
中删除私有 using Base::get
会导致有关从基 class 中隐藏函数的警告。所以不幸的是,这也不是一个理想的选择。
如果没有 using Derived<T>::get
,对 get
的调用必须在 MoreDerived
内限定,否则它就不是从属名称。
任何想法,我在这里做错了什么?
我相信这里适用的是[namespace.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. […]
(强调我的)结合 [namespace.udecl]/19:
A synonym created by a using-declaration has the usual accessibility for a member-declaration. […]
MoreDerived
中的 using 声明为 Derived::get
创建了一个同义词,它本身就是由成员函数 Derived::get
和成员函数模板 [=14] 组成的重载集的同义词=].后者在 MoreDerived
中的 using 声明点不可访问(因为它在 Derived
中是私有的)。因此,GCC 和 Clang 是正确的,这段代码不应该编译。将 Derived
中的 using 声明从 private 移动到 public 部分,例如
template<typename T> class Derived : public Base
{
public:
using Base::get;
const T& get() const;
};
解决了问题…
Michael Kenzel already explained nicely
[...] but making get fail to compile for Derived is actually a feature of the original code
尽管我不鼓励这种模式,因为您违反了 "is a" 关系,但以下内容可能对您有用:
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
的私有继承应该也能解决这个问题,如果这对你可行的话;如果没有,另一种选择可能是将模板 getter 移动到一个新的、单独的基础 class,然后它将被私下继承。
两种变体都会阻止
Derived<int> d;
static_cast<Base>(d).get<double>();
还有,如果这无论如何都没有意义的话。