c++ - 多重继承运算符()的重载解决方案

标签 c++ lambda language-lawyer multiple-inheritance overload-resolution

首先,考虑这段 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 声明,而不是另一个,那么所有三个编译器都将无法编译,因为它们只会考虑 引入的函数在重载解析期间使用,因此其中一个调用将由于类型不匹配而失败。记住这一点;我们稍后再讨论。

现在考虑以下代码:

#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 out 的情况。 , 或者如果 both are uncommented .但是如果你只注释掉一个而没有注释掉另一个,事情就是 suddenly different again :现在只有 MSVC 会像我预期的那样提示,而 Clang 和 gcc 都认为这很好 - 并且使用两个继承的成员来解决重载问题,尽管 using 只引入了一个!

问题 #2:在这种情况下谁是正确的?同样,我希望它是 MSVC,但是为什么 Clang 和 gcc 不同意呢?而且,更重要的是,为什么这与之前的代码片段不同?我希望 lambda 类型的行为与具有重载 operator()...

的手动定义类型完全相同

最佳答案

巴里的第一名是对的。你的 #2 遇到了一个极端情况:无捕获的非泛型 lambda 具有到函数指针的隐式转换,它在不匹配的情况下被使用。也就是说,给定

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++ - 多重继承运算符()的重载解决方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44429408/

相关文章:

c++ - 如何将 range::actions::insert 与 std::vector 一起使用

c++ - clang 中的 Clang : no warning with -Wdangling-gsl and curly braces initialization, 错误?

c# - Lambda 表达式 : Compiler Behaviour

c++ - int a[] = {1,2,};为什么允许在初始化列表中使用尾随逗号?

c++ - 带有 std::map<T*, U> 的程序是否具有明确定义的行为?

c++ - 是否有适用于 C++ 的跨平台命令行库?

c++ - 将整数识别为二维数组下标中的 float

java - 为什么 Java 不提示模棱两可的调用?

c# - 为 lambda 声明提供的参数数量不正确

c++ - 作为模板类型参数,为什么 type[N] 不匹配其专用版本---- template<class T> class S<T[]>