使用enable_if进行C++模板重载:使用g ++和clang进行不同的行为

V T*_*mer 14 c++ templates overloading c++14

在解析基类的模板化成员函数的重载期间,我观察到g ++(5.2.1-23)和clang(3.8.0)之间的不同行为-std=c++14.

#include <iostream>
#include <type_traits>

struct Base
{
  template <typename T>
  auto a(T t) -> void {
    std::cout<< "False\n";
  }
};

template <bool Bool>
struct Derived : public Base
{

  using Base::a;
  template <typename T, bool B = Bool>
  auto a(T t) -> std::enable_if_t<B, void>
  {
    std::cout<< "True\n";
  }
};

int main()
{
  Derived<true> d;
  d.a(1); // fails with g++, prints "true" with clang
  Derived<false> d2;
  d2.a(1); // fails with clang++, prints "false" with g++
}
Run Code Online (Sandbox Code Playgroud)

Derived<true>::a使用g ++ 调用失败并显示以下消息:

test.cc: In function ‘int main()’:
test.cc:28:8: error: call of overloaded ‘a(int)’ is ambiguous
   d.a(1);
        ^
test.cc:18:8: note: candidate: std::enable_if_t<B, void> Derived<Bool>::a(T) [with T = int; bool B = true; bool Bool = true; std::enable_if_t<B, void> = void]
   auto a(T t) -> std::enable_if_t<B, void>
        ^
test.cc:7:8: note: candidate: void Base::a(T) [with T = int]
   auto a(T t) -> void {
        ^
Run Code Online (Sandbox Code Playgroud)

Derived<false>::a使用以下消息使用clang ++ 调用失败:

test.cc:32:6: error: no matching member function for call to 'a'
  d2.a(1);
  ~~~^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.2.1/../../../../include/c++/5.2.1/type_traits:2388:44: note: candidate template ignored: disabled by 'enable_if' [with T = int, B = false]
    using enable_if_t = typename enable_if<_Cond, _Tp>::type;
                                           ^
Run Code Online (Sandbox Code Playgroud)

我的猜测是,他们对诠释的解释不同using Base::a;,而且在俚语中不予考虑,而在g ++中则考虑(可能太多).我认为会发生的是,如果Derivedtrue参数,则调用a()调度到Derived的实现,而如果参数是false,则调度调度Base::a.

他们都错了吗?谁是对的?我应该向谁提交错误报告?有人可以解释发生了什么吗?

谢谢

101*_*010 3

从 3.3.10/p3 开始名称隐藏 [basic.scope.hiding]:

\n\n
\n

在成员函数定义中,块作用域中名称的声明隐藏了具有相同名称的类成员的声明;见 3.3.7。派生类中成员的声明(第 10 条)隐藏了同名基类成员的声明;见10.2

\n
\n\n

另见 7.3.3/p15 using 声明 [namespace.udecl]:

\n\n
\n

当 using 声明将基类中的名称引入派生类作用域时,派生类中的成员函数和成员函数模板将覆盖和/或隐藏具有相同名称、参数的成员函数和成员函数模板-type-list (8.3.5)、\n cv 限定符和 ref 限定符(如果有)位于基类中(而不是冲突)。[ 注意:对于命名构造函数的 using 声明,请参阅 12.9。\xe2\x80\x94 尾注] [示例:

\n\n
struct B {\n  virtual void f(int);\n  virtual void f(char);\n  void g(int);\n  void h(int);\n};\nstruct D : B {\n  using B::f;\n  void f(int); // OK: D::f(int) overrides B::f(int);\n  using B::g;\n  void g(char); // OK\n  using B::h;\n  void h(int); // OK: D::h(int) hides B::h(int)\n};\nvoid k(D* p)\n{\n  p->f(1); // calls D::f(int)\n  p->f(\xe2\x80\x99a\xe2\x80\x99); // calls B::f(char)\n  p->g(1); // calls B::g(int)\n  p->g(\xe2\x80\x99a\xe2\x80\x99); // calls D::g(char)\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\x94 结束示例]

\n
\n\n

这在成员名称查找期间得到解决。因此,它位于模板参数推导之前。因此,正如评论中正确提到的,无论 SFINAE 的裁决如何,基本模板函数都会被隐藏。

\n\n

因此CLANG是正确的,GCC是错误的。

\n

  • 不,如果它是隐藏的,那么它就不在过载集中。名称查找找到一组候选者,您对其进行模板参数推导和重载解析。你不能说“哦,推导失败,所以我们去向超载集中添加更多成员”。 (3认同)
  • 它要么是隐藏的,不是重载集的一部分,要么是可见的,因此是重载集的一部分。“有时隐藏”是没有意义的。 (2认同)