如果复制列表初始化允许显式构造函数,可能会出现什么问题?
在 C++ 标准 §13.3.1.7 [over.match.list] 中,有以下规定:
在复制列表初始化中,如果选择
显式
构造函数,则初始化格式错误。
这就是为什么我们不能这样做的原因,例如,这样的事情:
struct foo {
// explicit because it can be called with one argument
explicit foo(std::string s, int x = 0);
private:
// ...
};
void f(foo x);
f({ "answer", 42 });
(请注意,这里发生的事情不是转换,即使构造函数是“隐式”。这是直接使用其构造函数初始化 foo
对象,除了 std::string
之外,这里没有任何转换。)
对我来说似乎很好。隐式转换不可能让我烦恼。
如果 { "answer", 42 }
可以初始化其他东西,编译器不会背叛我并做错误的事情:
struct bar {
// explicit because it can be called with one argument
explicit bar(std::string s, int x = 0);
private:
// ...
};
void f(foo x);
void f(bar x);
f({ "answer", 42 }); // error: ambiguous call
没有问题:调用不明确,代码不会编译,并且我必须明确选择过载。
f(bar { "answer", 42 }); // ok
既然明确规定了禁令,我感觉我在这里遗漏了一些东西。据我所知,列表初始化选择显式构造函数对我来说似乎不是问题:通过使用列表初始化语法,程序员已经表达了进行某种“转换”的愿望。
可能会出什么问题?我缺少什么?
In the C++ standard, §13.3.1.7 [over.match.list], the following is stated:
In copy-list-initialization, if an
explicit
constructor is chosen, the initialization is ill-formed.
This is the reason why we can't do, for example, something like this:
struct foo {
// explicit because it can be called with one argument
explicit foo(std::string s, int x = 0);
private:
// ...
};
void f(foo x);
f({ "answer", 42 });
(Note that what happens here is not a conversion, and it would not be one even if the constructor was "implicit". This is initialization of a foo
object using its constructor directly. Other than the std::string
, there is no conversion here.)
This seems perfectly fine to me. There's no way that an implicit conversion will bite me.
If { "answer", 42 }
can initialize something else, the compiler won't betray me and do the wrong thing:
struct bar {
// explicit because it can be called with one argument
explicit bar(std::string s, int x = 0);
private:
// ...
};
void f(foo x);
void f(bar x);
f({ "answer", 42 }); // error: ambiguous call
There's no problem: the call is ambiguous, the code won't compile, and I'll have to pick the overload explicitly.
f(bar { "answer", 42 }); // ok
Since the prohibition is explicitly stated, I have the feeling that I am missing something here. As far as I can see, list initialization picking explicit constructors doesn't seem like a problem to me: by using list initialization syntax the programmer is already expressing the desire to do some kind of "conversion".
What could go wrong? What am I missing?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
从概念上讲,复制列表初始化是将复合值转换为目标类型。提出措辞并解释原理的论文已经认为“副本列表初始化”中的术语“副本”是不幸的,因为它并没有真正传达其背后的实际原理。但保留它是为了与现有措辞兼容。
{10, 20}
对/元组值不应能够复制初始化String(int size, int Reserve)
,因为字符串不是对。显式构造函数被考虑但禁止使用。这在以下情况下是有意义的:
0
不传达字符串的值。因此,这会导致错误,因为两个构造函数都被考虑,但选择了显式
构造函数,而不是将0
视为空指针持续的。不幸的是,这也发生在跨函数的重载解析中,
由于不明确,这也是格式错误的。在 C++11 发布之前,有些人(包括我)认为这是不幸的,但并没有提出一篇论文提出对此进行更改(据我所知)。
Conceptually copy-list-initialization is the conversion of a compound value to a destination type. The paper that proposed wording and explained rationale already considered the term "copy" in "copy list initialization" unfortunate, since it doesn't really convey the actual rationale behind it. But it is kept for compatibility with existing wording. A
{10, 20}
pair/tuple value should not be able to copy initialize aString(int size, int reserve)
, because a string is not a pair.Explicit constructors are considered but forbidden to be used. This makes sense in cases as follows
0
does not convey the value of a string. So this results in an error because both constructors are considered, but anexplicit
constructor is selected, instead of the0
being treated as a null pointer constant.Unfortunately this also happens in overload resolution across functions
This is ill-formed too because of an ambiguity. Some people (including me) before C++11 was released thought that this is unfortunate, but didn't came up with a paper proposing a change regarding this (as far as I am aware).
不是因为“显式”可以阻止隐式转换,而您却要求它进行隐式转换吗?
如果您使用单参数构造函数指定了结构,您会问这个问题吗?
Isn't it because 'explicit' is there to stop implicit casting, and you're asking it to do an implicit cast?
Would you be asking the question if you has specified the structure with a single argument constructor?
本声明:
意味着很多事情。其中,这意味着它必须查看显式构造函数。毕竟,如果它看不到显式构造函数,它就无法选择它。当它寻找将括号列表转换为的候选项时,它必须从所有候选项中进行选择。即使是那些后来被发现是非法的。
如果重载解析导致多个函数同样可行,则会导致需要用户手动干预的不明确调用。
This statement:
means many things. Among them, it means that it must look at explicit constructors. After all, it can't select an explicit constructor if it can't look at it. When it looks for candidates to convert the braced list into, it must select from all candidates. Even the ones that will later be found to be illegal.
If overload resolution results in multiple functions being equally viable, then it results in an ambiguous call that requires manual user intervention.
据我了解,关键字 explicit 的真正目的是拒绝使用此构造函数进行隐式强制转换。
所以你问为什么显式构造函数不能用于隐式转换?显然是因为该构造函数的作者通过使用关键字 explicit 明确否认了它。您发布的标准中的引用只是指出 explicit 关键字也适用于初始值设定项列表(而不仅仅是某些类型的简单值)。
ADD:
更正确地说:与某些构造函数一起使用的关键字explicit的目的是绝对清楚地表明该构造函数在某个地方使用(即强制所有代码显式调用此构造函数)。
当
f
是函数名称时,像f({a,b})
这样的 IMO 语句与显式构造函数调用无关。绝对不清楚(并且依赖于上下文)此处使用哪个构造函数(以及什么类型),例如,它取决于存在的函数重载。另一方面,像
f(SomeType(a,b))
这样的东西是完全不同的东西 - 很明显,我们使用带有两个参数的SomeType
类型的构造函数a,b
并且我们使用函数f
重载,它是接受SomeType
类型的单个参数的最佳选择。因此,某些构造函数可以隐式使用,例如
f({a,b})
,而其他构造函数则要求读者绝对清楚它们的使用事实,这就是为什么我们将它们声明为显式< /强>。ADD2:
我的观点是:有时,即使不会出错,显式声明构造函数也是绝对有意义的。 IMO 构造函数是否显式更多的是其逻辑问题,而不是任何类型的警告。
例如
但是
As I understand the very purpose of the keyword explicit is denying implicit cast with this constructor.
So you are asking why explicit constructor cannot be used for implicit cast? Obviously because the author of that constructor explicitly denied it by using keyword explicit with it. The quote from the standard you've posted just states that explicit keyword applies also to initializer-lists (not just to simple values of some type).
ADD:
To say more correctly: the purpose of the keyword explicit used with some constructor is making it absolutely clear that this constructor is used in some place (i.e. forcing all the code to invoke this constructor explicitly).
And IMO statement like
f({a,b})
whenf
is a name of the function has nothing to do with explicit constructor call. It is absolutely unclear (and context dependent) which constructor (and what type) is used here, e.g. it depends on function overloads present.On the other hand something like
f(SomeType(a,b))
is totally different thing - it is absolutely clear that we use the constructor of typeSomeType
that takes two argumentsa,b
and that we use the functionf
overload that will be the best to accept single argument of typeSomeType
.So some constructors are OK for implicit use like
f({a,b})
and others require that the fact of their using is absolutely clear to the reader that is why we declare them explicit.ADD2:
My point is: Sometimes it absolutely makes sense to declare constructors explicit even if nothing could go wrong. IMO whether constructor is explicit is more a matter of its logic than caveats of any kind.
E.g.
But