为什么这段代码会为不同的编译器生成不同的输出?

Mat*_*vei 20 c++ vector initializer-list compiler-specific direct-initialization

我有以下代码:

#include <iostream>
#include <vector>

struct C {
  int a;

  C() : a(0) {}
  C(int a) : a(a) {}
};

std::ostream &operator<<(std::ostream &os, const C &c) {
  os << c.a;
  return os;
}

using T = std::vector<C>;

int main() {
  auto v = T({5});

  for (const auto &a : v) {
    std::cout << a << ", ";
  }
  std::cout << std::endl;

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果我使用 g++,它会打印:

#include <iostream>
#include <vector>

struct C {
  int a;

  C() : a(0) {}
  C(int a) : a(a) {}
};

std::ostream &operator<<(std::ostream &os, const C &c) {
  os << c.a;
  return os;
}

using T = std::vector<C>;

int main() {
  auto v = T({5});

  for (const auto &a : v) {
    std::cout << a << ", ";
  }
  std::cout << std::endl;

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果我使用 MS 编译器,它会打印:

5, 
Run Code Online (Sandbox Code Playgroud)

为什么结果不同?

没有()around {5},结果当然是一样的。

Adr*_*ica 13

可能是 g++ 编译器\xc2\xa7中的错误(而不是MSVC 编译器中的错误)。变量的类型v是(或者根据标准,应该是std::vector<C>)a ,包含 5 个默认初始化元素。

\n

为什么?因为您使用直接初始化(圆括号)而不是列表初始化(大括号,如 中auto v = T{ 5 })。这种直接初始化意味着调用构造函数,并且没有std::vector<T>需要单个T参数的构造函数。

\n

cppreference中提到了这种混乱的根源:

\n
\n

请注意,列表初始化构造函数 (10) 的存在意味着\n列表初始化和直接初始化会执行不同的操作:

\n
\n

现在,您可能认为编译器应该使用列表初始化构造函数(就像 g++ 那样)\xe2\x80\xa6,但它不应该这样做!该构造函数的参数不是类型而是T类型std::initializer_list<T>;因此,要调用该构造函数(使用直接初始化),您需要auto v = T({ { 5 } });(注意额外的大括号组)。\xe2\x80\xa0

\n

对于您的代码,auto v = T({ 5 });,最匹配的构造函数是链接的 cppreference 页面上的版本#3(默认为第二个第三参数),因此{ 5 }被视为size_t初始化为 的元素计数的值T()。事实上,根据同一页面,自 C++11 \xe2\x80\x93 以来,默认的第二个参数版本已被删除,因此您的调用的唯一匹配是构造函数版本 #4,它会产生相同的结果。

\n

事实上,clang(它为您的代码提供与 MSVC 相同的结果)对此发出警告(请在 Compiler Explorer 上查看):

\n
\n

警告:标量初始值设定项周围的大括号 [-Wbraced-scalar-init]

\n
\n

有多种方法可以强制初始化带有{5}成员的单元素向量。最简单的是使用列表初始化:

\n
auto v = T{ { 5 } }; // Or even: auto v = T{ 5 };\n
Run Code Online (Sandbox Code Playgroud)\n

另一种方法是添加一个显式count参数:

\n
auto v = T(1, { 5 }); // Or just: auto v = T(1, 5);\n
Run Code Online (Sandbox Code Playgroud)\n
\n

\xc2\xa7在涉及编译器应遵循的规则以符合 C++ 标准时,我并不声称自己是权威,因此我准备接受这可能(罕见的)歧义标准,而不是 g++ 编译器中的错误或缺陷。

\n

\xe2\x80\xa0请注意,虽然{ 5 } 可以用作有效值std::initializer_list<C>(就像 的情况一样auto v = T{ 5 };),但就函数调用(包括构造函数调用)的重载解析而言,它更适合单个size_t值; 请参阅函数调用中的单元素向量初始化(尤其是最上面的答案)。

\n