首页 文章

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

提问于
浏览
23

首先,考虑这个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结果here .

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

我希望结果与之前的情况相同,但it is not the case - 而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类型获取它 . 同样,您希望结果与之前的代码段保持一致;对于both using declarations are commented outboth are uncommented的情况也是如此 . 但是如果你只注释掉一个而不是另一个,那么事情就是suddenly different again:现在只有MSVC会按照我的预期抱怨,而Clang和gcc都认为这很好 - 并且使用两个继承成员进行重载解析,尽管只有一个被带来在 using

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

3 回答

  • 0

    巴里排名第一 . 你的#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] .

  • 4

    仅当 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 .

  • 7

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

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

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

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


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

    f.operator()(123);
    

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

    operator()应该是一个具有任意数量参数的非静态成员函数 . 它可以有默认参数 . 它实现了函数调用语法postfix-expression(expression-list opt),其中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() 存在”是什么意思?它可能意味着以下任何一种情况:

    • operator()T 中声明 .

    • T 范围内的 operator() 的非限定查找成功并对具有给定参数的查找集执行重载解析成功 .

    • 在调用上下文中对 T::operator() 的限定查找成功,并对该查找集执行重载解析,并且给定的参数成功 .

    • 别的什么?

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

相关问题