std::strings 的 std::initializer_list 的奇怪行为

Strange behavior of std::initializer_list of std::strings

这个问题很可能已经有人问过了,但我没有找到答案。

下面的代码使用 gcc 编译但在运行时崩溃,std::length_error (live)。

void test(const std::string &value) { std::cout << "string overload: " << value << std::endl; }

//void test(const std::vector<std::string> &) { std::cout << "vector overload" << std::endl; }

int main()
{
    test({"one", "two"});
}

从字符串的初始值设定项列表创建字符串的能力似乎存在争议,例如,无法创建上面代码中注释掉的重载。

但是即使允许这样的构造,为什么会导致失败呢?

它调用

string(const char* b, const char* e) 

字符串构造函数重载。

仅当 be 指向相同的字符串文字时才有效。否则它是未定义的行为。

对于初学者来说,没有使用接受初始化列表的构造函数,因为这样的构造函数看起来像

basic_string(initializer_list<charT>, const Allocator& = Allocator());
                              ^^^^^

因此编译器搜索另一个合适的构造函数并找到这样的构造函数。它是构造函数

template<class InputIterator>
basic_string(InputIterator begin, InputIterator end, const Allocator& a = Allocator());

即表达式 "one""two" 被认为是 const char *.

类型的迭代器

因此函数 test 具有未定义的行为。

你可以这样写(假设具有相同内容的字符串文字作为一个字符串文字存储在内存中,which is not guaranteed并且取决于所选的编译器选项)。

#include <iostream>
#include <string>

void test(const std::string &value) { std::cout << "string overload: " << value << std::endl; }

//void test(const std::vector<std::string> &) { std::cout << "vector overload" << std::endl; }

int main()
{
    test({ "one", "one" + 3 });
}

你会得到一个有效的结果。

string overload: one

注意这个构造

{ "one", "two" }

不是 std::initializer_list<T> 类型的对象。此构造没有类型。它是用作初始值设定项的 braced-init-list。编译器首先尝试使用具有类型 std::initializer_list 的第一个参数的构造函数来与此初始化程序一起使用。

例如,如果您将使用 class std::vector<const char *>,那么实际上编译器将使用它的构造函数 std::initializer_list 并相应地用这个花括号初始化列表初始化它的参数。例如

#include <iostream>
#include <vector>

int main()
{
    std::vector<const char *> v( { "one", "two" } );

    for ( const auto &s : v ) std::cout << s << ' ';
    std::cout << '\n';
}