调用带有braced-init列表的显式构造函数:是否有歧义? [英] Calling an explicit constructor with a braced-init list: ambiguous or not?

查看:290
本文介绍了调用带有braced-init列表的显式构造函数:是否有歧义?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下内容:

  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)
}

/ code> (4)清楚且明确地调用(2)。在clang上,(3)中的 paren 的构造明确调用(1) code>其中在gcc 5.2,它无法编译:

  main.cpp:在函数'int main ()':
main.cpp:11:19:error:重载的'B(<括号括起初化器列表>)'的调用是不明确的
B
^
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: :B(B&&)

我怀疑clang在这里是正确的,因为gcc中的歧义只能通过一个涉及隐式构造 B {1,2} 的路径,并将其传递给copy / move构造函数 - 但是该构造函数标记为 explicit ,因此不应允许这种隐式构造。

解决方案

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



复制列表初始化具有相当不直观的行为:它认为显式构造函数是可行的,直到重载解析完全完成,但是如果选择显式构造函数,则可以拒绝重载结果。后N4567草稿中的措辞[over.match.list] p1


在复制列表初始化中,如果 explicit 构造函数,
初始化是不成形的。 [注意:这不同于其他
情况(13.3.1.3,13.3.1.4),其中只有转换构造函数
被考虑用于复制初始化。此限制仅适用于
,如果此初始化是过载
分辨率的最终结果的一部分。 - 结束注释]







clang HEAD接受以下程序:

  #include< iostream> 
using namespace std;

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

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

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

这是除了注释掉对 f1 ,直接来自Bjarne Stroustrup的 N2532 - 统一初始化,第4章。 Johannes Schaub 标准讨论



同一章包含以下内容解释:


explicit 的真正优势是它呈现 f1(asdf) an
错误。一个问题是重载解析优先非显式
构造函数,以便 f(asdf)调用 f(String2)。我认为 f(asdf)
分辨率不太理想,因为
String2 可能不意味着解决歧义,赞成
String2 (至少不是在每个情况下,显式和非显式
构造函数这样发生),并且 String1 的作者肯定
没有。该规则优先于不使用显式的程序员。


对于我知道的所有内容, N2640 - 初始化列表 - 替代机制和原理是最后一篇文章包括这种过载分辨率的原理;它的后续 N2672 已投票进入C ++ 11草稿。



从其章节显式的含义:


使示例不成形的第一种方法是要求所有
构造函数(显式和非显式)被认为用于隐式
转换,但如果显式构造函数最终被选中,
程序是不成形的。这条规则可能会引起自己的惊喜;
例如:

  struct Matrix {
explicit Matrix(int n,int n);
};
矩阵转置(Matrix);

struct Pixel {
Pixel(int row,int col);
};
像素转置(像素);

Pixel p = transpose({x,y}); //错误。

第二种方法是在查看
时忽略显式构造函数转换,但是当
实际上选择转换构造函数时包括它们:如果一个显式的
构造函数被选中,程序是不成形的。这个
替代方法允许最后一个(Pixel-vs-Matrix)示例按照预期工作
(选择 transpose(Pixel)
原始示例( X x4 = {10}; )格式错误。


虽然本文提出使用第二种方法,但其措辞似乎有缺陷 - 在我对措辞的解释中,它不产生文章的理论部分中概述的行为。






p>在OP中初始化一个变量当然包含更多的措辞,但是考虑到clang和gcc之间的行为差​​异在我的答案中的第一个示例程序是相同的,我认为这涵盖了要点。 / p>

Consider the following:

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)
}

The construction of brace in (4) clearly and unambiguously calls (2). On clang, the construction of paren in (3) unambiguously calls (1) where as on gcc 5.2, it fails to compile with:

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&&)

Which compiler is right? I suspect clang is correct here, as the ambiguity in gcc can only arise through a path that involves implicitly constructing B{1,2} and passing that to the copy/move constructor - yet that constructor is marked explicit, so such implicit construction should not be allowed.

解决方案

As far as I can tell, this is a clang bug.

Copy-list-initialization has a rather unintuitive behaviour: It considers explicit constructors as viable until overload resolution is completely finished, but can then reject the overload result if an explicit constructor is chosen. The wording in a post-N4567 draft, [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 accepts the following program:

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

Which is, except for commenting out the call to f1, straight from Bjarne Stroustrup's N2532 - Uniform initialization, Chapter 4. Thanks to Johannes Schaub for showing me this paper on std-discussion.

The same chapter contains the following explanation:

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.


For all I know, N2640 - Initializer Lists — Alternative Mechanism and Rationale is the last paper that includes rationale for this kind of overload resolution; it successor N2672 was voted into the C++11 draft.

From its chapter "The Meaning Of Explicit":

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.

While this paper proposes to use the second approach, its wording seems to be flawed - in my interpretation of the wording, it doesn't produce the behaviour outlined in the rationale part of the paper. The wording is revised in N2672 to use the first approach, but I couldn't find any discussion about why this was changed.


There is of course slightly more wording involved in initializing a variable as in the OP, but considering the difference in behaviour between clang and gcc is the same for the first sample program in my answer, I think this covers the main points.

这篇关于调用带有braced-init列表的显式构造函数:是否有歧义?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆