为什么将字符串初始化为单个字符的代码会调用initializer_list构造函数?

6

最近我在处理一个C++项目时,遇到了一些字符串构造函数的边缘情况,但是我还不能完全理解。相关代码(你可以在这里运行)如下:

#include <iostream>
#include <string>
using namespace std;

int main() {
    string directParens(1, '*');
    string directBraces{1, '*'};
    string indirectBraces = {1, '*'};

    cout << directParens.size() << endl;   // 1
    cout << directBraces.size() << endl;   // 2
    cout << indirectBraces.size() << endl; // 2
    return 0;
}

字符串的花括号初始化版本最终会在其中包含两个字符,即一个数值为1的char后面跟着一个星号。

我不明白为什么字符串的花括号初始化版本会调用initializer_list构造函数,而不是输入大小和字符的构造函数。 initializer_list构造函数的签名如下:

basic_string(std::initializer_list<CharT> init, 
             const Allocator& alloc = Allocator());

考虑到stringbasic_string char的别名,具体签名应该是:
string(std::initializer_list<char> init, 
       const Allocator& alloc = Allocator());

包含int类型和char类型元素的初始化器{1,'*'}是如何匹配此构造函数的?我原本以为std::initializer_list中的所有字面量都必须具有相同的类型 - 难道这是不正确的吗?


1
请注意,对于常量表达式,如果转换后的结果适合目标类型,则存在缩小转换的例外情况。参考链接 - Shafik Yaghmour
1个回答

15
括号内既包含int类型又包含char类型的初始化器{1,*}如何匹配该构造函数?
因为字面值1和字符'*'都可以转换为char类型而不使用收缩转换。 因此,它们符合调用initializer_list构造函数的条件。并且initializer_list构造函数在使用列表初始化时始终具有优先级。如果可以从参数中调用initializer_list构造函数,则会调用它。
除非你打算将列表中的元素作为容器的元素,否则永远不要使用大括号初始化列表来初始化容器。如果你想将它们作为构造函数参数,请使用构造函数。

啊,明白了。如果所有元素都可以在不进行缩小转换的情况下转换为基础类型,则大括号初始化程序将被解释为initializer_list。确认一下,这是否意味着{3, 3.0}可以被解释为initializer_list<double>,但不能被解释为initializer_list<int>?而{INT_MAX,'*'}可以是initializer_list<char>吗? - templatetypedef
1
@templatetypedef:嗯,{INT_MAX,'*'}的例子不能initializer_list<char>,因为INT_MAX不能被转换为char而不进行缩小转换。但除此之外,您是正确的。 - Nicol Bolas
哦,我以为窄化转换适用于类型,而不是。这解决了我的疑惑!谢谢! - templatetypedef
6
重载决议并不关心转换是否为缩小转换。它仍会选择initializer_list构造函数,这将导致程序不合法。 - T.C.
@T.C.:哇,你成功让我更加讨厌列表初始化了。 - Nicol Bolas
1
“除非你打算将列表中的元素作为容器的元素,否则不要在容器中使用花括号初始化列表。如果你想将它们作为构造函数参数,则应该使用构造函数。” - 我认为这句话应该加粗。尽管如此,还是给这个提示点赞。 - WhiZTiM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接