【问题标题】:Strange behavior of std::initializer_list of std::stringsstd::strings 的 std::initializer_list 的奇怪行为
【发布时间】:2019-08-30 10:31:19
【问题描述】:

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

下面的代码使用 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"});
}

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

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

【问题讨论】:

  • initializer_list 版本的std::string 仅适用于字符列表,不适用于字符串列表。使用字符串列表,您可以获得对象的标准列表初始化。注释重载是可以的,只要不模棱两可。 IE。如果列表有超过 2 个元素。
  • 注意(因为这不是主要问题):这里的问题来自"one""two" 不是std::strings。您可以使用 test({{"one"}, {"two"}}); 或使用 C++17 字符串文字 test({"one"s, "two"s});(使用 using namespace std::literals;)。任一will work
  • @Max Langhof,谢谢!

标签: c++ constructor initializer-list overload-resolution constructor-overloading


【解决方案1】:

它调用

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

字符串 ctor 过载。

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

【讨论】:

  • 好的,谢谢。有什么想法如何实现向量重载的选择,最好不改变调用的语法?
  • 我在cppreference 中找不到这个重载。它在做什么?
  • 以某种有趣的方式得出了相同的结论:Live Demo on coliru。好的,好的,调试器会更简单——但我得到了一个很好的演示。 ;-)
  • @Yksisarvinen template&lt; class InputIt &gt; basic_string( InputIt first, InputIt last, const Allocator&amp; alloc = Allocator() );
  • @Yuriy 你说的“用向量重载的选择”是什么意思?你想做什么?
【解决方案2】:

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

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&lt;T&gt; 类型的对象。这种结构没有类型。它是一个braced-init-list,用作初始化器。只需编译器首先尝试使用具有 std::initializer_list 类型的第一个参数的构造函数来与此初始化器一起使用。

例如,如果您将使用 std::vector&lt;const char *&gt; 类,那么编译器将使用其构造函数与 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';
}

【讨论】:

  • { "one", "one" + 3 } 这不是依赖于两个"one" 都被编译为指向同一个地址的事实吗?这是按标准授予的吗?
  • 抱歉,在我阅读其余内容之前,这引起了我的注意。 ;-) 我自己也太偏执了,无法依赖...
  • @LightnessRacesinOrbit 这取决于编译器选项。通常,您可以使用编译器选项选择编译器相对于字符串文字的行为。
  • 是的,就像 GCC 中的 -fno-merge-constants
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-06
  • 2020-06-11
  • 2013-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多