简体   繁体   English

多重继承运算符的重载分辨率()

[英]Overload resolution for multiply inherited operator()

First, consider this C++ code: 首先,考虑一下这个C ++代码:

#include <stdio.h>

struct foo_int {
    void print(int x) {
        printf("int %d\n", x);
    }    
};

struct foo_str {
    void print(const char* x) {
        printf("str %s\n", x);
    }    
};

struct foo : foo_int, foo_str {
    //using foo_int::print;
    //using foo_str::print;
};

int main() {
    foo f;
    f.print(123);
    f.print("abc");
}

As expected according to the Standard, this fails to compile, because print is considered separately in each base class for the purpose of overload resolution, and thus the calls are ambiguous. 正如根据标准所预期的那样,这无法编译,因为为了重载解析的目的,在每个基类中单独考虑print ,因此调用是不明确的。 This is the case on Clang (4.0), gcc (6.3) and MSVC (17.0) - see godbolt results here . 这是Clang(4.0),gcc(6.3)和MSVC(17.0)的情况 - 请参阅此处的 godbolt结果。

Now consider the following snippet, the sole difference of which is that we use operator() instead of print : 现在考虑下面的代码片段,唯一的区别是我们使用operator()而不是print

#include <stdio.h>

struct foo_int {
    void operator() (int x) {
        printf("int %d\n", x);
    }    
};

struct foo_str {
    void operator() (const char* x) {
        printf("str %s\n", x);
    }    
};

struct foo : foo_int, foo_str {
    //using foo_int::operator();
    //using foo_str::operator();
};

int main() {
    foo f;
    f(123);
    f("abc");
}

I would expect the results to be identical to the previous case, but it is not the case - while gcc still complains, Clang and MSVC can compile this fine! 我希望结果与之前的情况相同,但事实并非如此 - 而gcc仍然抱怨,Clang和MSVC可以编译这个罚款!

Question #1: who is correct in this case? 问题#1:在这种情况下谁是正确的? I expect it to be gcc, but the fact that two other unrelated compilers give a consistently different result here makes me wonder whether I'm missing something in the Standard, and things are different for operators when they're not invoked using function syntax. 我希望它是gcc,但事实上其他两个不相关的编译器在这里给出了一致的不同结果让我想知道我是否遗漏了标准中的某些内容,并且当操作符未使用函数语法调用时,它们会有所不同。

Also note that if you only uncomment one of the using declarations, but not the other, then all three compilers will fail to compile, because they will only consider the function brought in by using during overload resolution, and thus one of the calls will fail due to type mismatch. 另请注意,如果您只取消其中一个using声明而不取消另一个声明,则所有三个编译器都将无法编译,因为它们只会考虑在重载解析期间using的函数,因此其中一个调用将失败由于类型不匹配。 Remember this; 记住这一点; we'll get back to it later. 我们稍后再回过头来看看。

Now consider the following code: 现在考虑以下代码:

#include <stdio.h>

auto print_int = [](int x) {
    printf("int %d\n", x);
};
typedef decltype(print_int) foo_int;

auto print_str = [](const char* x) {
    printf("str %s\n", x);
};
typedef decltype(print_str) foo_str;

struct foo : foo_int, foo_str {
    //using foo_int::operator();
    //using foo_str::operator();
    foo(): foo_int(print_int), foo_str(print_str) {}
};

int main() {
    foo f;
    f(123);
    f("foo");
}

Again, same as before, except now we don't define operator() explicitly, but instead get it from a lambda type. 同样,和以前一样,除了现在我们没有显式定义operator() ,而是从lambda类型获取它。 Again, you'd expect the results to be consistent with the previous snippet; 同样,您希望结果与之前的代码段保持一致; and this is true for the case where both using declarations are commented out , or if both are uncommented . 对于using声明都被注释掉 ,或两者都被取消注释的情况都是如此。 But if you only comment out one but not the other, things are suddenly different again : now only MSVC complains as I would expect it to, while Clang and gcc both think it's fine - and use both inherited members for overload resolution, despite only one being brought in by using ! 但是如果你只注释掉一个而不是另一个,那么事情就会突然变得不同了 :现在只有MSVC会按照我的预期抱怨,而Clang和gcc都认为这很好 - 并且使用两个继承的成员进行重载解析,尽管只有一个被using带入!

Question #2: who is correct in this case? 问题2:在这种情况下谁是正确的? Again, I'd expect it to be MSVC, but then why do both Clang and gcc disagree? 再一次,我希望它是MSVC,但那么为什么Clang和gcc都不同意? And, more importantly, why this is different from the previous snippet? 而且,更重要的是,为什么这与前面的代码片段不同? I would expect the lambda type to behave exactly the same as a manually defined type with overloaded operator() ... 我希望lambda类型的行为与带有重载operator()的手动定义类型完全相同...

Barry got #1 right. 巴里排名第一。 Your #2 hit a corner case: captureless nongeneric lambdas have an implicit conversion to function pointer, which got used in the mismatch case. 你的#2命中了一个角落:无捕获的非泛型lambdas有一个隐式转换为函数指针,它在不匹配的情况下使用。 That is, given 也就是说,给定

struct foo : foo_int, foo_str {
    using foo_int::operator();
    //using foo_str::operator();
    foo(): foo_int(print_int), foo_str(print_str) {}
} f;

using fptr_str = void(*)(const char*);

f("hello") is equivalent to f.operator fptr_str()("hello") , converting the foo to an pointer-to-function and calling that. f("hello")等效于f.operator fptr_str()("hello") ,将foo转换为指向函数的指针并调用它。 If you compile at -O0 you can actually see the call to the conversion function in the assembly before it gets optimized away. 如果在-O0编译,实际上可以在程序集优化之前看到对程序集中转换函数的调用。 Put an init-capture in print_str , and you'll see an error since the implicit conversion goes away. print_str放置一个init-capture ,你会看到一个错误,因为隐式转换消失了。

For more, see [over.call.object] . 有关更多信息,请参阅[over.call.object]

The rule for name lookup in base classes of a class C only happens if C itself doesn't directly contain the name is [class.member.lookup]/6 : 只有当C本身不直接包含名称为[class.member.lookup] / 6时,才会发生类C基类中的名称查找规则:

The following steps define the result of merging lookup set S(f,Bi) into the intermediate S(f,C) : 以下步骤定义将查找集S(f,Bi)合并到中间S(f,C)

  • If each of the subobject members of S(f,Bi) is a base class subobject of at least one of the subobject members of S(f,C), or if S(f,Bi) is empty, S(f,C) is unchanged and the merge is complete. 如果S(f,Bi)的每个子对象成员是S(f,C)的至少一个子对象成员的基类子对象,或者如果S(f,Bi)为空,则S(f,C) )没有变化,合并完成。 Conversely, if each of the subobject members of S(f,C) is a base class subobject of at least one of the subobject members of S(f,Bi), or if S(f,C) is empty, the new S(f,C) is a copy of S(f,Bi). 相反,如果S(f,C)的每个子对象成员是S(f,Bi)的至少一个子对象成员的基类子对象,或者如果S(f,C)为空,则新的S (f,C)是S(f,Bi)的副本。

  • Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous : the new S(f,C) is a lookup set with an invalid declaration set and the union of the subobject sets. 否则,如果S(f,Bi)和S(f,C)的声明集不同,则合并是不明确的 :新的S(f,C)是具有无效声明集的查找集和子对象的并集集。 In subsequent merges, an invalid declaration set is considered different from any other. 在后续合并中,无效的声明集被认为与其他任何不同。

  • Otherwise, the new S(f,C) is a lookup set with the shared set of declarations and the union of the subobject sets. 否则,新的S(f,C)是具有共享声明集和子对象集的并集的查找集。

If we have two base classes, that each declare the same name, that the derived class does not bring in with a using-declaration, lookup of that name in the derived class would run afoul of that second bullet point and the lookup should fail. 如果我们有两个基类,每个都声明了相同的名称,派生类没有引入using声明,那么在派生类中查找该名称将与第二个项目符号点相冲突,并且查找应该失败。 All of your examples are basically the same in this regard. 在这方面,您的所有示例基本相同。

Question #1: who is correct in this case? 问题#1:在这种情况下谁是正确的?

gcc is correct. gcc是对的。 The only difference between print and operator() is the name that we're looking up. printoperator()之间的唯一区别是我们正在查找的名称。

Question #2: who is correct in this case? 问题2:在这种情况下谁是正确的?

This is the same question as #1 - except we have lambdas (which give you unnamed class types with overload operator() ) instead of explicit class types. 这是与#1相同的问题 - 除了我们有lambdas(它给你带有重载operator()未命名类类型)而不是显式类类型。 The code should be ill-formed for the same reason. 出于同样的原因,代码应该是格式错误的。 At least for gcc, this is bug 58820 . 至少对于gcc来说,这是bug 58820

Your analysis of the first code is incorrect. 您对第一个代码的分析不正确。 There is no overload resolution. 没有重载解决方案。

The name lookup process occurs wholly before overload resolution. 名称查找过程完全在重载解析之前发生。 Name lookup determines which scope an id-expression is resolved to. 名称查找确定要解析id-expression的范围。

If a unique scope is found via the name look-up rules, then overload resolution begins: all instances of that name within that scope form the overload set. 如果通过名称查找规则找到唯一范围, 开始重载解析:该范围内该名称的所有实例形成重载集。

But in your code, name lookup fails. 但在您的代码中,名称查找失败。 The name is not declared in foo , so base classes are searched. 该名称未在foo声明,因此将搜索基类。 If the name is found in more than one immediate base class then the program is ill-formed and the error message describes it as an ambiguous name. 如果在多个直接基类中找到该名称,则该程序格式错误,并且错误消息将其描述为不明确的名称。


The name lookup rules do not have special cases for overloaded operators. 名称查找规则没有重载运算符的特殊情况。 You should find that the code: 你应该找到代码:

f.operator()(123);

fails for the same reason as f.print failed. 失败的原因与f.print失败的原因相同。 However, there is another issue in your second code. 但是,第二个代码中还有另一个问题。 f(123) is NOT defined as always meaning f.operator()(123); f(123) f.operator()(123);定义为f.operator()(123); . In fact the definition in C++14 is in [over.call]: 事实上,C ++ 14中的定义是[over.call]:

operator() shall be a non-static member function with an arbitrary number of parameters. operator()应该是一个具有任意数量参数的非静态成员函数。 It can have default arguments. 它可以有默认参数。 It implements the function call syntax 它实现了函数调用语法

postfix-expression ( expression-list opt ) postfix-expression(表达式列表选项)

where the postfix-expression evaluates to a class object and the possibly empty expression-list matches the parameter list of an operator() member function of the class. 其中postfix-expression计算为一个类对象,而可能为空的表达式列表匹配该类的operator()成员函数的参数列表。 Thus, a call x(arg1,...) is interpreted as x.operator()(arg1, ...) for a class object x of type T if T::operator()(T1, T2, T3) exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3.3). 因此,如果T::operator()(T1, T2, T3)存在,则调用x(arg1,...)被解释为类型为T的类对象x的x.operator()(arg1, ...)如果操作符被重载决策机制选为最佳匹配函数(13.3.3)。

This actually seems an imprecise specification to me so I can understand different compilers coming out with different results. 这实际上对我来说似乎是一个不精确的规范,所以我可以理解不同的编译器会出现不同的结果。 What is T1,T2,T3? 什么是T1,T2,T3? Does it mean the types of the arguments? 这是否意味着参数的类型? (I suspect not). (我怀疑不是)。 What are T1,T2,T3 when multiple operator() function exist, only taking one argument? 什么是T1,T2,T3当存在多个operator()函数时,只接受一个参数?

And what is meant by "if T::operator() exists" anyway? 什么是“if T::operator()存在”呢? It could perhaps mean any of the following: 它可能意味着以下任何一种情况:

  1. operator() is declared in T . operator()T声明。
  2. Unqualified lookup of operator() in the scope of T succeeds and performing overload resolution on that lookup set with the given arguments succeeds. T的范围内对operator()非限定查找成功,并且对具有给定参数的查找集执行重载解析成功。
  3. Qualified lookup of T::operator() in the calling context succeeds and performing overload resolution on that lookup set with the given arguments succeeds. 调用上下文中对T::operator()限定查找成功,并对该查找集执行重载解析,并使给定参数成功。
  4. Something else? 别的什么?

To proceed from here (for me anyway) I would like to understand why the standard didn't simply say that f(123) means f.operator()(123); 从这里开始(无论如何),我想理解为什么标准不是简单地说f(123)意味着f.operator()(123); , the former being ill-formed if and only if the latter is ill-formed. ,前者是形成不良的,当且仅当后者形成不良时。 The motivation behind the actual wording might reveal the intent and therefore which compiler's behaviour matches the intent. 实际措辞背后的动机可能会揭示意图,因此编译器的行为与意图相匹配。

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

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