c++ - 使用花括号初始化列表调用显式构造函数 : ambiguous or not?

标签 c++ c++11 gcc language-lawyer

考虑以下几点:

struct A {
    A(int, int) { }
};

struct B {
    B(A ) { }                   // (1)
    explicit B(int, int ) { }   // (2)
};

int main() {
    B paren({1, 2});   // (3)
    B brace{1, 2};     // (4)
}

(4)brace 的构造清楚明确地调用了(2)。在 clang 上,(3)paren 的构造明确调用了 (1),而在 gcc 5.2 上,它无法编译:

main.cpp: In function 'int main()':
main.cpp:11:19: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous
     B paren({1, 2});
                   ^
main.cpp:6:5: note: candidate: B::B(A)
     B(A ) { }  
     ^
main.cpp:5:8: note: candidate: constexpr B::B(const B&)
 struct B {
        ^
main.cpp:5:8: note: candidate: constexpr B::B(B&&)

哪个编译器是正确的?我怀疑 clang 在这里是正确的,因为 gcc 中的歧义只能通过涉及隐式构造 B{1,2} 并将其传递给复制/移动构造函数的路径出现 - 但该构造函数被标记显式,因此不应允许这种隐式构造。

最佳答案

据我所知,这是一个 clang 错误

Copy-list-initialization 有一个相当不直观的行为:它认为显式构造函数在重载决议完全完成之前是可行的,但如果选择了显式构造函数,则可以拒绝重载结果。 N4567 后草案中的措辞,[over.match.list]p1

In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations (13.3.1.3, 13.3.1.4), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. — end note ]


clang HEAD 接受以下程序:

#include <iostream>
using namespace std;

struct String1 {
    explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
    String2(const char*) { cout << "String2\n"; }
};

void f1(String1) { cout << "f1(String1)\n"; }
void f2(String2) { cout << "f2(String2)\n"; }
void f(String1) { cout << "f(String1)\n"; }
void f(String2) { cout << "f(String2)\n"; }

int main()
{
    //f1( {"asdf"} );
    f2( {"asdf"} );
    f( {"asdf"} );
}

也就是说,除了注释掉对 f1 的调用外,直接来自 Bjarne Stroustrup 的 N2532 - Uniform initialization 第 4 章。感谢 Johannes Schaub 向我展示了这篇关于 std-discussion 的论文。

同一章节包含以下解释:

The real advantage of explicit is that it renders f1("asdf") an error. A problem is that overload resolution “prefers” non-explicit constructors, so that f("asdf") calls f(String2). I consider the resolution of f("asdf") less than ideal because the writer of String2 probably didn’t mean to resolve ambiguities in favor of String2 (at least not in every case where explicit and non-explicit constructors occur like this) and the writer of String1 certainly didn’t. The rule favors “sloppy programmers” who don’t use explicit.


据我所知,N2640 - Initializer Lists — Alternative Mechanism and Rationale 是最后一篇包含这种重载决议原理的论文;它的继任者 N2672 被选入 C++11 草案。

摘自“显式的含义”一章:

A first approach to make the example ill-formed is to require that all constructors (explicit and non-explicit) are considered for implicit conversions, but if an explicit constructor ends up being selected, that program is ill-formed. This rule may introduce its own surprises; for example:

struct Matrix {
    explicit Matrix(int n, int n);
};
Matrix transpose(Matrix);

struct Pixel {
    Pixel(int row, int col);
};
Pixel transpose(Pixel);

Pixel p = transpose({x, y}); // Error.

A second approach is to ignore the explicit constructors when looking for the viability of an implicit conversion, but to include them when actually selecting the converting constructor: If an explicit constructor ends up being selected, the program is ill-formed. This alternative approach allows the last (Pixel-vs-Matrix) example to work as expected (transpose(Pixel) is selected), while making the original example ("X x4 = { 10 };") ill-formed.

虽然这篇论文建议使用第二种方法,但它的措辞似乎有缺陷——在我对措辞的解释中,它不会产生论文的基本原理部分概述的行为。 N2672 中的措辞被修改为使用第一种方法,但我找不到任何关于为什么改变的讨论。


当然,在 OP 中初始化变量涉及的措辞略多,但考虑到 clang 和 gcc 之间的行为差​​异对于我的回答中的第一个示例程序是相同的,我认为这涵盖了要点。

关于c++ - 使用花括号初始化列表调用显式构造函数 : ambiguous or not?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49264846/

相关文章:

c++ - 指针解引用运算符 ( (*) vs -> )

c++ - ifstream 获取从 char 到 string 的行更改输出

c++ - 将推断类型传递给 std::find_if lambda 函数

c++ - 将双 vector 拆分为相等的部分

c++ - 如何使用 MinGW 编译 C++ std::thread 代码?

c - 在 elf-gcc 中,exp() 只在第一次调用时正常工作,之后就不行了

c++ - 错误 : assigning to 'char' from incompatible type 'const char *'

c++ - 如何使用一次广播在 MPI 中传输不同类型的 vector

c++ - 如何使函数模板仅适用于特定命名空间中的类型?

xcode:如何使用 GCC_VERSION 字段指定 clang 版本?