为什么 ctor 中的 std::initializer_list 没有按预期运行?
Why does std::initializer_list in ctor not behave as expected?
#include <vector>
int main()
{
auto v = std::vector{std::vector<int>{}};
return v.front().empty(); // error
}
然而,根据 Scott Meyers 的 Effective Modern C++(强调原文):
If, however, one or more constructors declare a parameter of type
std::initializer_list
, calls using the braced initialization syntax
strongly prefer the overloads taking std::initializer_lists
. Strongly.
If there's any way for compilers to construe a call using a braced
initializer to be a constructor taking a std::initializer_list
,
compilers will employ that interpretation.
所以,我认为 std::vector{std::vector<int>{}};
应该生成 std::vector<std::vector<int>>
而不是 std::vector<int>
的对象。
谁错了?为什么?
auto v = std::vector{std::vector<int>{}};
实际上创建了一个 std::vector<int>
因为它使用了 std::vector
复制构造函数。它被编译器解释为:
auto vTemp = std::vector<int>{};
auto v = std::vector<int>( vTemp );
所以 v
最终成为 std::vector<int>
,而不是 std::vector<std::vector<int>>
。
正如“P Kramer”和“Marek P”在评论中所报告的那样,以下语法将帮助任何编译器完成您所期望的:
auto v = std::vector{{std::vector<int>{}}};
auto v = std::vector<std::vector<int>>{ std::vector<int>{} };
Meyers 大部分是正确的(例外是如果存在默认构造函数,T{}
是值初始化),但他的陈述是关于 overload 分辨率的。这发生在 CTAD 之后,它选择 class(以及构造函数集)来使用。
CTAD 并不“更喜欢”初始化列表构造函数,因为它更喜欢 复制 而不是包装 std::vector
或 std::optional
等可嵌套模板。 (可以用演绎指南覆盖它,但标准库使用默认值,正如人们可能期望的那样。)这在某种程度上是有道理的,因为它可以防止创建像 std::optional<std::optional<int>>
这样的奇怪类型,但它会使通用代码更难编写因为它给
template<class T> void f(T x) {
std::vector v{x};
// …
}
一个取决于其参数类型的含义,采用 不规则 和 非单射 方式。特别是,v
可能是 std::vector<int>
与 T
=int
或 T
=std::vector<int>
,尽管 std::vector<std::deque<int>>
如果 T
=std::deque<int>
。不幸的是,基于其他一些类型计算一种类型的工具不能在通用上下文中使用。
#include <vector>
int main()
{
auto v = std::vector{std::vector<int>{}};
return v.front().empty(); // error
}
然而,根据 Scott Meyers 的 Effective Modern C++(强调原文):
If, however, one or more constructors declare a parameter of type
std::initializer_list
, calls using the braced initialization syntax strongly prefer the overloads takingstd::initializer_lists
. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking astd::initializer_list
, compilers will employ that interpretation.
所以,我认为 std::vector{std::vector<int>{}};
应该生成 std::vector<std::vector<int>>
而不是 std::vector<int>
的对象。
谁错了?为什么?
auto v = std::vector{std::vector<int>{}};
实际上创建了一个 std::vector<int>
因为它使用了 std::vector
复制构造函数。它被编译器解释为:
auto vTemp = std::vector<int>{};
auto v = std::vector<int>( vTemp );
所以 v
最终成为 std::vector<int>
,而不是 std::vector<std::vector<int>>
。
正如“P Kramer”和“Marek P”在评论中所报告的那样,以下语法将帮助任何编译器完成您所期望的:
auto v = std::vector{{std::vector<int>{}}};
auto v = std::vector<std::vector<int>>{ std::vector<int>{} };
Meyers 大部分是正确的(例外是如果存在默认构造函数,T{}
是值初始化),但他的陈述是关于 overload 分辨率的。这发生在 CTAD 之后,它选择 class(以及构造函数集)来使用。
CTAD 并不“更喜欢”初始化列表构造函数,因为它更喜欢 复制 而不是包装 std::vector
或 std::optional
等可嵌套模板。 (可以用演绎指南覆盖它,但标准库使用默认值,正如人们可能期望的那样。)这在某种程度上是有道理的,因为它可以防止创建像 std::optional<std::optional<int>>
这样的奇怪类型,但它会使通用代码更难编写因为它给
template<class T> void f(T x) {
std::vector v{x};
// …
}
一个取决于其参数类型的含义,采用 不规则 和 非单射 方式。特别是,v
可能是 std::vector<int>
与 T
=int
或 T
=std::vector<int>
,尽管 std::vector<std::deque<int>>
如果 T
=std::deque<int>
。不幸的是,基于其他一些类型计算一种类型的工具不能在通用上下文中使用。