繁体   English   中英

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

[英]Overload resolution for multiply inherited operator()

首先,考虑一下这个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");
}

正如根据标准所预期的那样,这无法编译,因为为了重载解析的目的,在每个基类中单独考虑print ,因此调用是不明确的。 这是Clang(4.0),gcc(6.3)和MSVC(17.0)的情况 - 请参阅此处的 godbolt结果。

现在考虑下面的代码片段,唯一的区别是我们使用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");
}

我希望结果与之前的情况相同,但事实并非如此 - 而gcc仍然抱怨,Clang和MSVC可以编译这个罚款!

问题#1:在这种情况下谁是正确的? 我希望它是gcc,但事实上其他两个不相关的编译器在这里给出了一致的不同结果让我想知道我是否遗漏了标准中的某些内容,并且当操作符未使用函数语法调用时,它们会有所不同。

另请注意,如果您只取消其中一个using声明而不取消另一个声明,则所有三个编译器都将无法编译,因为它们只会考虑在重载解析期间using的函数,因此其中一个调用将失败由于类型不匹配。 记住这一点; 我们稍后再回过头来看看。

现在考虑以下代码:

#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");
}

同样,和以前一样,除了现在我们没有显式定义operator() ,而是从lambda类型获取它。 同样,您希望结果与之前的代码段保持一致; 对于using声明都被注释掉 ,或两者都被取消注释的情况都是如此。 但是如果你只注释掉一个而不是另一个,那么事情就会突然变得不同了 :现在只有MSVC会按照我的预期抱怨,而Clang和gcc都认为这很好 - 并且使用两个继承的成员进行重载解析,尽管只有一个被using带入!

问题2:在这种情况下谁是正确的? 再一次,我希望它是MSVC,但那么为什么Clang和gcc都不同意? 而且,更重要的是,为什么这与前面的代码片段不同? 我希望lambda类型的行为与带有重载operator()的手动定义类型完全相同...

巴里排名第一。 你的#2命中了一个角落:无捕获的非泛型lambdas有一个隐式转换为函数指针,它在不匹配的情况下使用。 也就是说,给定

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")等效于f.operator fptr_str()("hello") ,将foo转换为指向函数的指针并调用它。 如果在-O0编译,实际上可以在程序集优化之前看到对程序集中转换函数的调用。 print_str放置一个init-capture ,你会看到一个错误,因为隐式转换消失了。

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

只有当C本身不直接包含名称为[class.member.lookup] / 6时,才会发生类C基类中的名称查找规则:

以下步骤定义将查找集S(f,Bi)合并到中间S(f,C)

  • 如果S(f,Bi)的每个子对象成员是S(f,C)的至少一个子对象成员的基类子对象,或者如果S(f,Bi)为空,则S(f,C) )没有变化,合并完成。 相反,如果S(f,C)的每个子对象成员是S(f,Bi)的至少一个子对象成员的基类子对象,或者如果S(f,C)为空,则新的S (f,C)是S(f,Bi)的副本。

  • 否则,如果S(f,Bi)和S(f,C)的声明集不同,则合并是不明确的 :新的S(f,C)是具有无效声明集的查找集和子对象的并集集。 在后续合并中,无效的声明集被认为与其他任何不同。

  • 否则,新的S(f,C)是具有共享声明集和子对象集的并集的查找集。

如果我们有两个基类,每个都声明了相同的名称,派生类没有引入using声明,那么在派生类中查找该名称将与第二个项目符号点相冲突,并且查找应该失败。 在这方面,您的所有示例基本相同。

问题#1:在这种情况下谁是正确的?

gcc是对的。 printoperator()之间的唯一区别是我们正在查找的名称。

问题2:在这种情况下谁是正确的?

这是与#1相同的问题 - 除了我们有lambdas(它给你带有重载operator()未命名类类型)而不是显式类类型。 出于同样的原因,代码应该是格式错误的。 至少对于gcc来说,这是bug 58820

您对第一个代码的分析不正确。 没有重载解决方案。

名称查找过程完全在重载解析之前发生。 名称查找确定要解析id-expression的范围。

如果通过名称查找规则找到唯一范围, 开始重载解析:该范围内该名称的所有实例形成重载集。

但在您的代码中,名称查找失败。 该名称未在foo声明,因此将搜索基类。 如果在多个直接基类中找到该名称,则该程序格式错误,并且错误消息将其描述为不明确的名称。


名称查找规则没有重载运算符的特殊情况。 你应该找到代码:

f.operator()(123);

失败的原因与f.print失败的原因相同。 但是,第二个代码中还有另一个问题。 f(123) f.operator()(123);定义为f.operator()(123); 事实上,C ++ 14中的定义是[over.call]:

operator()应该是一个具有任意数量参数的非静态成员函数。 它可以有默认参数。 它实现了函数调用语法

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

其中postfix-expression计算为一个类对象,而可能为空的表达式列表匹配该类的operator()成员函数的参数列表。 因此,如果T::operator()(T1, T2, T3)存在,则调用x(arg1,...)被解释为类型为T的类对象x的x.operator()(arg1, ...)如果操作符被重载决策机制选为最佳匹配函数(13.3.3)。

这实际上对我来说似乎是一个不精确的规范,所以我可以理解不同的编译器会出现不同的结果。 什么是T1,T2,T3? 这是否意味着参数的类型? (我怀疑不是)。 什么是T1,T2,T3当存在多个operator()函数时,只接受一个参数?

什么是“if T::operator()存在”呢? 它可能意味着以下任何一种情况:

  1. operator()T声明。
  2. T的范围内对operator()非限定查找成功,并且对具有给定参数的查找集执行重载解析成功。
  3. 调用上下文中对T::operator()限定查找成功,并对该查找集执行重载解析,并使给定参数成功。
  4. 别的什么?

从这里开始(无论如何),我想理解为什么标准不是简单地说f(123)意味着f.operator()(123); ,前者是形成不良的,当且仅当后者形成不良时。 实际措辞背后的动机可能会揭示意图,因此编译器的行为与意图相匹配。

暂无
暂无

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

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