调用带有braced-init列表的显式构造函数:是否有歧义? [英] Calling an explicit constructor with a braced-init list: ambiguous or not?
问题描述
请考虑以下内容:
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 rendersf1("asdf")
an error. A problem is that overload resolution "prefers" non-explicit
constructors, so thatf("asdf")
callsf(String2)
. I consider the resolution off("asdf")
less than ideal because the writer ofString2
probably didn’t mean to resolve ambiguities in favor ofString2
(at least not in every case where explicit and non-explicit constructors occur like this) and the writer ofString1
certainly didn’t. The rule favors "sloppy programmers" who don’t useexplicit
.
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屋!