C++ 带有列表初始化的Ambigous构造函数调用

C++ 带有列表初始化的Ambigous构造函数调用,c++,c++11,list-initialization,C++,C++11,List Initialization,b的构造会产生以下错误: struct A { A(int) {} }; struct B { B(A) {} }; int main() { B b({0}); } 函数“int main()”中的: 24:9:错误:重载“B()”的调用不明确 24:9:注:候选人为: 11:2:注:B::B(A) 10:8:注:常量表达式B::B(常量B&) 10:8:注:constexpr B::B(B&&) 我希望调用B::B(A),为什么在这种情况下它是不明确的?B({

b
的构造会产生以下错误:

struct A {
    A(int) {}
};

struct B {
    B(A) {}
};

int main() {
    B b({0});
}
函数“int main()”中的
:
24:9:错误:重载“B()”的调用不明确
24:9:注:候选人为:
11:2:注:B::B(A)
10:8:注:常量表达式B::B(常量B&)
10:8:注:constexpr B::B(B&&)
我希望调用
B::B(A)
,为什么在这种情况下它是不明确的?

B({0})
可能导致调用以下任一项:

  • B::B(A)

  • 复制
    B
    的构造函数:从
    {0}
    和 然后将其复制到
    b

  • 因此产生了歧义

    如果调用
    bb{0}
    ,则可以解决此问题,它直接使用定义的构造函数,而不涉及复制构造函数

    编辑:

    关于第2点的有效性:


    B
    有一个接受
    a
    的构造函数。现在,
    A
    可以由
    int
    构造。另外,
    int
    可以通过初始化列表构造。这就是为什么这是一个有效的案例。如果
    A
    的构造函数是
    显式的
    ,那么从
    {0}
    int
    的自动强制转换将失败,不会产生歧义。

    代码可以很好地编译

    这不应该是含糊不清的呼叫。对于正在调用的
    B
    的复制/移动构造函数,然后对于
    B({0})需要执行以下步骤:

  • 通过
    A::A(int)
  • 在步骤1中由
    B::B(A)
  • 通过复制/移动
    b
    的构造函数在步骤2中构造
    b
    ,从
    b
    构造

  • 这意味着需要两个用户定义的转换(步骤#1和#2),但在一个隐式转换序列中不允许这样做。

    给定一个类,
    a
    ,具有用户定义的构造函数:

    In function 'int main()':
    24:9: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous
    24:9: note: candidates are:
    11:2: note: B::B(A)
    10:8: note: constexpr B::B(const B&)
    10:8: note: constexpr B::B(B&&)
    
    另一个为
    B
    ,接受
    A
    作为构造函数参数:

    struct A
    {
        A(int) {}
    };
    
    然后,为了执行如下初始化:

    struct B
    {
        B(A) {}
    };
    
    <>编译器必须考虑以下候选:

    B b({0});
    
    正在尝试查找从
    {0}
    到每个参数的隐式转换序列

    请注意,
    bb({0})
    不列出initialize
    B
    ——列表初始化应用于构造函数参数本身

    由于参数是初始值设定项列表,因此根据列表初始化序列定义参数与参数匹配所需的隐式转换序列:

    当一个参数是初始值设定项列表([dcl.init.list])时,它不是一个表达式,特殊规则适用于将其转换为参数类型

    内容如下:

    […],如果参数为非聚合类X,且重载解析符合13.3.1.7,则选择单个 执行参数初始值设定项列表中X类型对象初始化的最佳X构造函数, 隐式转换序列是具有第二个标准转换的用户定义转换序列 对身份转换进行排序。如果多个构造函数是可行的,但没有一个比其他构造函数更好,则 隐式转换序列是不明确的转换序列。允许用户定义的转换 用于将初始值设定项列表元素转换为构造函数参数类型,第13.3.3.1条中注明的情况除外

    要使#1可行,以下调用必须有效:

    B(A);         // #1
    B(const B&);  // #2
    B(B&&);       // #3
    
    A a = {0};
    
    这是正确的,因为:

    -如果没有找到可行的初始值设定项列表构造函数,将再次执行重载解析,其中候选函数是类
    T
    的所有构造函数,参数列表由初始值设定项列表的元素组成

    i、 例如,类
    A
    有一个接受
    int
    参数的构造函数

    要使#2成为有效候选,以下调用必须有效:

    B(A);         // #1
    B(const B&);  // #2
    B(B&&);       // #3
    
    A a = {0};
    
    其中根据:

    当引用类型的参数未直接绑定到参数表达式时,转换序列是根据[over.best.ics]将参数表达式转换为引用类型所需的序列。从概念上讲,此转换序列对应于使用参数表达式初始化引用类型的临时副本。顶级cv资格认证中的任何差异均包含在初始化中,不构成转换

    翻译为:

    const B& b = {0};
    
    再次强调以下几点:

    允许用户定义的转换将初始值设定项列表元素转换为构造函数参数类型[…]

    允许编译器使用用户定义的转换:

    B b = {0};
    
    将参数
    0
    转换为
    B
    的构造函数参数
    A


    对于候选人#3,同样的推理适用于#2。最终,编译器无法在上述隐式转换序列{引文需要}之间进行选择,并报告歧义。

    编译器是这么说的,但没有解释为什么会这样做happens@PiotrSkotnicki不知道你所说的“为什么会发生”是什么意思。当重载解析发现不止一种可能性时,就会出现歧义,这在这里是很明显的。哦,那么歧义是由歧义调用引起的?我永远也猜不到我没有遵循选项2。为什么
    B
    是从带有0的初始值设定项列表构造的?我可能很容易漏掉一些东西,但至少这是不明显的,值得解释
    B
    没有任何接受整数或初始化列表的构造函数,甚至没有默认构造函数!“如果A的构造函数是显式的,那么从{0}到int的自动转换将失败,不会产生歧义。”,您尝试过吗?@ashen您使用的编译器(和版本)是什么?这是正确的