error: no viable overloading with clang, compiles with gcc

error: no viable overloading with clang, compiles with gcc

以下程序可以用 g++(版本 10.1.0)编译,但不能用 clang++(10.0.0)编译

#include <iostream>

template <typename U>
struct A { U x; };

namespace tools {
  template <typename U>
  void operator+=(A<U>& lhs, const A<U>& rhs) { lhs.x += rhs.x; }
}

namespace impl {
  template <typename U = int>
  void f() {
    A<U> a{3};
    A<U> b{2};
    a += b;
    std::cout << a.x << std::endl;
  }
}

namespace impl {
  using namespace tools;
}

int main()
{
  impl::f();
}

错误是:

name.cpp:16:7: error: no viable overloaded '+='
    a += b;
    ~ ^  ~
name.cpp:27:9: note: in instantiation of function template specialization 'impl::f<int>' requested here
  impl::f();

显然,将 using namespace tools 部分移到模板函数 impl::f() 之前可以消除 clang 的错误。

补充说明 这里很重要的一点是 f 是一个模板函数。没有模板参数,代码既不能用 gcc 编译,也不能用 clang 编译。

这里哪个编译器是正确的? gcc 还是 clang?

根据 this,clang 似乎就在这里。简而言之 - 您正在扩展您的命名空间,但 using namespace 应该 'propagate' 仅向前扩展到此扩展。

A using-directive specifies that the names in the nominated namespace can be used in the scope in which the using-directive appears after the using-directive. During unqualified name lookup ([basic.lookup.unqual]), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace. [ Note: In this context, “contains” means “contains directly or indirectly”. — end note ]

Clang 是对的:非限定依赖名称查找仅考虑在模板定义点可见的声明

在您的示例中,operator+= 是函数模板 f 中的从属名称,在这种情况下,a += b; 调用的非限定名称查找仅考虑可见的声明 在函数模板f的定义处。由于 tools 名称空间作为指定名称空间添加到 impl 的定义点 f 之后,不合格。名称查找将看不到从 tools 引入的声明,并且无法看到 tools::operator+=。因此,Clang 就在这里,而 GCC as well as MSVC 没有拒绝代码是错误的。

GCC 的这种行为似乎仅在从属名称引用运算符函数时才会出现,而如果我们用命名函数替换运算符,GCC 也会拒绝该代码。

被 Clang 拒绝,被 GCC 接受:

struct Dummy{};

namespace ns_g {
    template <typename T>
    bool operator!(T) { return true; } 
}  // namespace ns_f

namespace ns_f {
    template <typename T>
    void f() { 
        (void)(!T{});
    }
    
    // Add ns_g as a nominated namespace to ns_f
    // _after_ point of definition of ns_f::f.
    using namespace ns_g;
}  // namespace ns_f

int main() {
    ns_f::f<Dummy>();
    return 0;
}

同时被 Clang 和 GCC 拒绝:

struct Dummy{};

namespace ns_g {
    template <typename T>
    bool g(T) { return true; } 
}  // namespace ns_f

namespace ns_f {
    template <typename T>
    void f() { 
        (void)(g(T{}));
    }
    
    // Add ns_g as a nominated namespace to ns_f
    // _after_ point of definition of ns_f::f.
    using namespace ns_g;
}  // namespace ns_f

int main() {
    ns_f::f<Dummy>();
    return 0;
}

其中,对于后者,GCC 甚至给了我们一个注释:

note: 'template<class T> bool ns_g::g(T)' declared here, later in the translation unit.

仅此不一致就暗示 GCC 在前一个示例中是错误的,我们可能不会 Clang's language compatibility page 明确提到某些版本的 GCC 可能接受无效代码:

Language Compatibility

[...]

Unqualified lookup in templates

Some versions of GCC accept the following invalid code: [...]

即使 Clang 指出的特定示例也被更新的 GCC 版本拒绝,这个问题的上下文是相同的。


在 GCC 上打开错误报告

请注意,OP(和回答者)对一个类似的 SO 问题(我在所有答案都落在这个问题上很久之后才发现),这个问题可能是重复的:

  • Dependent name lookup in function template: clang rejects, gcc accepts

已提交关于 GCC 的错误报告,但尚未提交 claimed/addressed:


(以下所有 ISO 标准参考均参考 N4659: March 2017 post-Kona working draft/C++17 DIS

标准参考文献

即使[temp.res]/9声明[摘录,强调我的]:

[temp.res]/9 When looking for the declaration of a name used in a template definition, the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) are used for non-dependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known ([temp.dep]). [ Example: ... ] [...]

[temp.dep.res]/1 很明显,只有在模板定义点可见的声明才会被考虑用于非限定(依赖)名称查找 [emphasis 我的]:

[temp.dep.res]/1 In resolving dependent names, names from the following sources are considered:

  • (1.1) Declarations that are visible at the point of definition of the template.
  • (1.2) Declarations from namespaces associated with the types of the function arguments both from the instantiation context ([temp.point]) and from the definition context.

[temp.dep.candidate]/1中重复的一个事实[强调我的]:

[temp.dep.candidate]/1 For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) except that:

  • (1.1) For the part of the lookup using unqualified name lookup, only function declarations from the template definition context are found.
  • (1.2) For the part of the lookup using associated namespaces ([basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.

其中 模板定义上下文 的措辞被用来代替 模板定义点 ,但据我所知,它们是等价的。

根据[namespace.udir]/2[强调我的]:

A using-directive specifies that the names in the nominated namespace can be used in the scope in which the using-directive appears after the using-directive. During unqualified name lookup, the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace. [ Note: In this context, “contains” means “contains directly or indirectly”.  — end note ]

将 using 指令 ​​放在函数模板 的定义点之后 f 等同于简单地声明一个名称 after 相同的定义点,正如预期的那样,以下修改的示例被 Clang 拒绝但被 GCC 接受:

struct Dummy{};

namespace ns_f {
    template <typename T>
    void f() { 
        (void)(!T{});
    }

    template <typename T>
    bool operator!(T) { return true; } 
}  // namespace ns_f

int main() {
    ns_f::f<Dummy>();
    return 0;
} 

最后,请注意上面 [temp.dep.candidate]/1 中的 ADL (1.2) 不适用于此处,因为 ADL 不会继续封闭范围。


可以在没有实例化的情况下诊断非依赖构造

Additional note An important point here is that f is a template function. Without template parameters the code would not compile neither with gcc, nor with clang.

如果要将A做成非模板class,比如说

struct A { int x; };

然后 [temp.res]/8.3 应用,程序格式错误,不需要诊断:

[temp.res]/8 Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:

[...]

(8.3) a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or

[...]

代码格式错误,因为不依赖参数的非限定名称查找部分是在模板定义上下文中执行的。所以 Clang 是对的,并且已经报告了 GCC 错误 (bug #70099)

接下来是长篇大论

在您的示例代码中,有一些地方必须标记,以便讨论:

namespace impl {
  template <typename U = int>
  void f() {                       // (1) point of definition of the template f
    A<U> a{3};
    A<U> b{2};
    a += b;                        //  call operator += with arguments of dependent type A<U> 
    std::cout << a.x << std::endl;
  }
}

namespace impl {
  using namespace tools;          // using directive     
}

int main()
{
  impl::f();
}                                 // (2) point of instantiation of impl::f<int>

在模板 f (1) 的定义中,使用类型 A<U> 的参数执行对运算符 += 的调用。 A<U>dependent type, so operator += is a dependent name.

[temp.dep.res]/1 描述如何查找 operator +=

For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules from the template definition context ([basic.lookup.unqual], [basic.lookup.argdep]). [ Note: For the part of the lookup using associated namespaces ([basic.lookup.argdep]), function declarations found in the template instantiation context are found by this lookup, as described in [basic.lookup.argdep]. — end note ][...]

执行了两次查找。

非参数相关的不合格名称查找 [basic.lookup.unqual]

此查找是从模板定义上下文执行的。 “来自模板定义上下文”是指模板定义点的上下文。术语“context”指的是查找上下文。如果模板 f 首先在命名空间 impl 中声明,然后在全局命名空间范围内定义,则不合格的名称查找仍会找到命名空间 impl 的成员。这就是规则 [temp.dep.res]/1 使用“模板定义上下文”而不是简单地使用“模板定义点”的原因。

此查找是从 (1) 执行的,它没有找到在命名空间 tools 中定义的 operator +=。 using 指令晚于 (1) 出现,并且无效。

参数相关名称查找 (ADL) [basic.lookup.argdep]

ADL 在实例化 (2) 时执行。所以它是在using指令之后实现的。然而,ADL 只考虑与参数类型关联的命名空间。参数的类型为 A<int>,模板 A 是全局命名空间的成员,因此 ADL 只能找到此命名空间的成员。

在 (2) 处没有 operator += 在全局命名空间范围内声明。所以 ADL 也找不到 operator +=.

的声明