简体   繁体   English

错误:clang 没有可行的重载,用 gcc 编译

[英]error: no viable overloading with clang, compiles with gcc

The following program compiles fine with g++ (version 10.1.0) but not with clang++ (10.0.0)以下程序可以使用 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();
}

The error is:错误是:

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

Clearly, moving the using namespace tools part before the template function impl::f() removes the error of clang.显然,将using namespace tools部分移到模板 function impl::f()之前会消除 clang 的错误。

Additional note An important point here is that f is a template function.附加说明这里重要的一点是f是一个模板 function。 Without template parameters the code compiles neither with gcc, nor with clang.如果没有模板参数,代码既不能用 gcc 编译,也不能用 clang 编译。

What compiler is correct here?什么编译器在这里是正确的? gcc or clang? gcc 还是 clang?

Seems clang is right here according to this .似乎 clang 就在这里 In short - you are extending your namespace but using namespace should 'propagate' to this extension only forward.简而言之 - 你正在扩展你的命名空间,但using namespace应该只向前“传播”到这个扩展。

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. using-directive 指定指定命名空间中的名称可以在 using-directive 出现在 using-directive 之后的 scope 中使用。 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.在非限定名称查找 ([basic.lookup.unqual]) 期间,名称看起来好像它们是在最近的封闭命名空间中声明的,其中包含使用指令和指定命名空间。 [ Note: In this context, “contains” means “contains directly or indirectly”. [注:在此上下文中,“包含”是指“直接或间接包含”。 — end note ] ——尾注]

The code is ill-formed because the part of unqualified name look-up that is not argument dependent is performed in the template definition context.该代码格式错误,因为不依赖于参数的非限定名称查找部分是在模板定义上下文中执行的。 So Clang is right and the GCC bug is already reported ( bug #70099 )所以 Clang 是正确的,并且已经报告了 GCC 错误(错误#70099

What followes is the long explanation.接下来是长篇大论。

Inside your exemple code there are some place that must be marked, to allow the discussion:在您的示例代码中有一些必须标记的地方,以便进行讨论:

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>

At the definition of the template f (1), the call to the operator += is performed with arguments of type A<U> .在模板f (1) 的定义中,使用A<U>类型的 arguments 执行对运算符 += 的调用。 A<U> is a dependent type , so operator += is a dependent name . A<U>是一个从属类型,所以operator +=是一个从属名称

[temp.dep.res]/1 describe how operator += is looked up: [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]).对于后缀表达式是从属名称的 function 调用,候选函数是使用模板定义上下文([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]. [ 注意:对于使用关联命名空间 ([basic.lookup.argdep]) 的查找部分,此查找可找到在模板实例化上下文中找到的 function 声明,如 [basic.lookup.argdep] 中所述。 — end note ][...] — 尾注][...]

There are two look-ups that are performed.执行了两个查找。

Non argument dependent unqualified name look up [basic.lookup.unqual] .不依赖于参数的非限定名称查找[basic.lookup.unqual]

This look-up is performed from the template definition context .此查找是从模板定义上下文中执行的。 " from the template definition context " means the context at the point of definition of the template. 来自模板定义上下文”是指模板定义点的上下文 The term " context " refers to the look-up context.术语“上下文”指的是查找上下文。 If the template f was first declared in namespace impl and then defined in the global namespace scope, unqualified name look-up would still find members of namespace impl .如果模板f首先在命名空间impl中声明,然后在全局命名空间 scope 中定义,则非限定名称查找仍会找到命名空间impl的成员。 This is why the rule [temp.dep.res]/1 use " the template definition context " and not simply " template definition point ".这就是为什么规则 [temp.dep.res]/1 使用“模板定义上下文”而不是简单的“模板定义点”。

This look-up is performed from (1) and it does not find the operator += defined in namespace tools .此查找是从 (1) 执行的,它没有找到命名空间tools中定义的operator += The using directive is appears later than (1), and has no effect. using 指令出现在 (1) 之后,并且没有效果。

Argument dependent name look-up (ADL) [basic.lookup.argdep]参数依赖名称查找 (ADL) [basic.lookup.argdep]

ADL is performed at the point of instantiation (2). ADL 在实例化点执行 (2)。 So it is realized after the using directive.所以它是在 using 指令之后实现的。 Nevertheless, ADL only considers namespace associated to the type of the arguments.尽管如此,ADL 只考虑与 arguments 类型关联的命名空间。 The arguments have type A<int> , the template A is a member of the global namespace, so only members of this namespace can be find by ADL. arguments 的类型为A<int> ,模板A是全局命名空间的成员,因此 ADL 只能找到该命名空间的成员。

At (2) there are no operator += declared in the global namespace scope.在 (2) 处,在全局命名空间 scope 中没有声明operator += So ADL also fails to find a declaration for operator += .所以 ADL 也找不到operator +=的声明。

Clang is right: unqualified dependent name lookup considers only declarations that are visible at the point of definition of the template Clang 是对的:不合格的依赖名称查找只考虑在模板定义点可见的声明

In your example, the operator+= is a dependent name in the function template f , in which case unqualified name lookup for the a += b;在您的示例中, operator+=是 function 模板f中的从属名称,在这种情况下, a += b; call considers only declarations that are visible at the point of definition of the function template f . call 仅考虑在 function 模板f的定义点可见的声明。 As the tools namespace is added as a nominated namespace to impl only after the point of definition of f , unqual.由于tools命名空间是作为指定命名空间添加到impl的,因此仅f的定义点之后进行,不合格。 name lookup will not see the declarations brought in from tools , and will fail to tools::operator+= .名称查找将看不到从tools引入的声明,并且将无法找到tools::operator+= Thus, Clang is right here, whereas GCC as well as MSVC , is wrong in not rejecting the code.因此, Clang 就在这里,而 GCC以及 MSVC不拒绝代码是错误的。

This behaviour for GCC seems to only be present when the dependent name refers to an operator function, whereas if we replace the operator with a named function, GCC also rejects the code. This behaviour for GCC seems to only be present when the dependent name refers to an operator function, whereas if we replace the operator with a named function, GCC also rejects the code.

Rejected by Clang, accepted by 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;
}

Rejected by both Clang and by GCC:被 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;
}

where, for the latter, GCC even gives us a note that:其中,对于后者,GCC 甚至给我们一个注释:

note: ' template<class T> bool ns_g::g(T) ' declared here, later in the translation unit .注意: ' template<class T> bool ns_g::g(T) ' 在这里声明,稍后在翻译单元中。

This inconsistency alone hints that GCC is wrong in the former example, and we may not that Clang's language compatibility page explicitly mentions that some versions of GCC may accept invalid code:仅此不一致就暗示 GCC 在前一个示例中是错误的,我们可能不会Clang 的语言兼容性页面明确提到 GCC 的某些版本可能接受无效代码:

Language Compatibility语言兼容性

[...] [...]

Unqualified lookup in templates模板中的不合格查找

Some versions of GCC accept the following invalid code: [...] GCC 的某些版本接受以下无效代码:[...]

Even if the particular example pointed out by Clang is rejected also by more recent GCC versions, the context of this questions is the same.即使 Clang 指出的特定示例也被最近的 GCC 版本拒绝,这个问题的上下文是相同的。


Open bug report on GCC打开关于 GCC 的错误报告

Note that the OP (and answerer) to a similar SO question (which I found long after all the answers landed on this question), to which this question is probably a duplicate:请注意,OP(和回答者)对类似的 SO 问题(我在所有答案都落在这个问题上很久之后发现),这个问题可能是重复的:

submitted a bug report on GCC that is yet to be claimed/addressed:提交了关于 GCC 的错误报告,但尚未声明/解决:


(All ISO Standard references below refer to N4659: March 2017 post-Kona working draft/C++17 DIS ) (以下所有 ISO 标准参考参考N4659:2017 年 3 月 Kona 后工作草案/C++17 DIS

Standard references标准参考

Even if [temp.res]/9 states [extract, emphasis mine]:即使[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. [temp.res]/9在查找模板定义中使用的名称声明时,通常的查找规则([basic.lookup.unqual]、[basic.lookup.argdep])用于非依赖名称。 The lookup of names dependent on the template parameters is postponed until the actual template argument is known ([temp.dep]).依赖模板参数的名称查找被推迟到知道实际的模板参数([temp.dep])。 [ Example: ... ] [...] [ 例子: ... ] [...]

[temp.dep.res]/1 is clear that only declarations that are visible at the point of definition of the template are considered for non-qualified (dependent) name lookup [ emphasis mine]: [temp.dep.res]/1很明显,只有在模板定义时可见的声明才被考虑用于非限定(依赖)名称查找 [强调我的]:

[temp.dep.res]/1 In resolving dependent names, names from the following sources are considered: [temp.dep.res]/1在解析依赖名称时,会考虑以下来源的名称:

  • (1.1) Declarations that are visible at the point of definition of the template. (1.1)在模板定义处可见的声明。
  • (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. (1.2) 来自与 function arguments 类型关联的命名空间的声明,既来自实例化上下文([temp.point]),也来自定义上下文。

a fact that is repeated in [temp.dep.candidate]/1 [ emphasis mine]:[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: [temp.dep.candidate]/1对于后缀表达式是从属名称的 function 调用,使用通常的查找规则([basic.lookup.unqual]、[basic.lookup.argdep])找到候选函数除了那个:

  • (1.1) For the part of the lookup using unqualified name lookup , only function declarations from the template definition context are found . (1.1) 对于使用非限定名称查找的部分,仅找到模板定义上下文中的 function 声明
  • (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. (1.2) 对于使用关联命名空间 ([basic.lookup.argdep]) 的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的 function 声明。

where the wording template definition context is used instead of point of definition of the template , but afaik these are equivalent.其中使用措辞模板定义上下文而不是模板的定义点,但 afaik 这些是等价的。

As per [namespace.udir]/2 [ emphasis mine]:根据[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. using-directive 指定指定命名空间中的名称可以在 using-directive 出现在 using-directive 之后的 scope 中使用。 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 ] ——尾注]

placing the using directive after the point of definition of the function template f is equivalent to simply declaring a name after the same point of definition, and as expected, the following modified example is rejected by Clang but accepted by GCC:将 using 指令放在 function 模板f的定义点之后,相当于在同一定义点之后简单地声明一个名称,并且正如预期的那样,以下修改示例被 Clang 拒绝但被 Z32D8B23Z:7C58A26D82A50B 接受

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;
} 

Finally, note that ADL, (1.2) in [temp.dep.candidate]/1 above, does not apply here, as ADL do not proceed to enclosing scopes.最后,请注意上面[temp.dep.candidate]/1中的 ADL (1.2) 不适用于此处,因为 ADL 不会继续封闭范围。


Non-dependent constructs may be diagnosed without instantiations可以在没有实例化的情况下诊断非依赖结构

Additional note An important point here is that f is a template function.附加说明这里重要的一点是f是一个模板 function。 Without template parameters the code would not compile neither with gcc, nor with clang.如果没有模板参数,代码既不能用 gcc 编译,也不能用 clang 编译。

If A were to be made into a non-template class, say如果要将A制成非模板 class,例如

struct A { int x; };

then [temp.res]/8.3 applies, and the program is ill-formed, no diagnostic required:然后[temp.res]/8.3适用,并且程序格式错误,不需要诊断:

[temp.res]/8 Knowing which names are type names allows the syntax of every template to be checked. [temp.res]/8知道哪些名称是类型名称可以检查每个模板的语法。 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 (8.3)由于不依赖于模板参数的构造,紧随其定义的模板的假设实例化将是格式错误的,或

[...] [...]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM