You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为何std::initializer_list在std::vector构造函数中的表现不符合预期?

为什么std::vector{std::vector<int>{}}被推断为std::vector<int>而非std::vector<std::vector<int>>

先看你的代码:

#include <vector>
int main() {
    auto v = std::vector{std::vector<int>{}};
    return v.front().empty(); // error: 'int' has no member 'empty'
}

你预期vstd::vector<std::vector<int>>,但编译器推断它为std::vector<int>,这看起来和Scott Meyers在《Effective Modern C++》里的观点冲突——他说有std::initializer_list构造函数的类,使用大括号初始化时会优先选择这个重载。

问题出在哪里?

Scott的观点没错,但它有一个关键前提:你使用的是括号内的初始化器列表(braced-init-list),而不是单个对象。我们来拆解两种情况:

1. 当你使用嵌套大括号(真正的初始化器列表)

如果你写成:

auto v = std::vector{{std::vector<int>{}}};

此时外层大括号里是一个braced-init-list(包含一个std::vector<int>对象),编译器会优先匹配std::vectorinitializer_list构造函数,正确推断Tstd::vector<int>v就会是你预期的std::vector<std::vector<int>>类型。

2. 你的代码中的情况:单个对象作为初始化器

你写的std::vector{std::vector<int>{}}里,外层大括号的内容是单个std::vector<int>对象,不是初始化器列表。这时候Scott的优先级规则不适用,编译器会按照类模板参数推断(CTAD)的常规逻辑来寻找匹配的构造函数:

  • 如果尝试推断T = std::vector<int>:我们需要构造std::vector<std::vector<int>>,但它的构造函数里没有能直接接受单个std::vector<int>对象的重载(initializer_list构造需要初始化器列表,移动构造需要同类型的右值,输入迭代器构造需要两个参数),所以这个推断不成立。
  • 如果尝试推断T = int:我们需要构造std::vector<int>,它的移动构造函数正好接受std::vector<int>&&类型的参数,而std::vector<int>{}正是一个std::vector<int>的右值,完全匹配!

所以编译器会选择这个可行的推断,将v推断为std::vector<int>,自然v.front()int类型,调用empty()就会报错。

总结

你误解了Scott观点的适用场景:它只针对初始化器是braced-init-list(即{元素1, 元素2,...})的情况。当你用单个对象作为大括号初始化的内容时,编译器会回到常规的重载解析和模板参数推断逻辑,优先选择有匹配构造函数的推断结果。

内容的提问来源于stack exchange,提问作者xmllmx

火山引擎 最新活动