C++ - initializer_list意义、使用

统一初始化

C++11引入一个小概念:为了使 std::initializer_list 工作,引入了一个叫统一初始化,又名“大括号初始化 { }”

#include <iostream>
#include <vector>
#include <array>

int main() {
    int i{42};
    double d{3.14};
    float e{0.114};
    char c{'A'};
    bool b{true};
    std::string s{"Hello, world!"};
    std::vector<int> v{1, 2, 3, 4, 5};
    std::array<int, 3> a{1, 2, 3};
    std::initializer_list<int> il{1, 2, 3};
}

傻春:不是,初始化还写个{},我用个=不比你这个方便,花里胡哨!

为了杜绝以下现象的发生,C++的统一初始化,就是一个对于隐式转换的一个补丁(((在新语言里面,这种转换多半被禁止,例如kotlin需要手动v.toInt()

int i = 3.14;✅ 
// Clang-Tidy: Narrowing conversion from constant 'double' to 'int' 

int j{ 3.14 }; 🛑❌
// error: narrowing conversion of ‘3.1400000000000001e+0’ from ‘double’ to ‘int’ [-Wnarrowing]

大括号初始化,在您编写C++项目的时候,Clion会推荐您使用,甚至补全部分代码。

initializer_list

说回来,std::initializer_list<T> 类型的对象是轻量代理对象,提供对 const T 类型对象数组的访问(可能分配于只读内存)。

#include <cassert>
#include <initializer_list>
#include <iostream>
#include <vector>
 
template<class T>
struct S
{
    std::vector<T> v;
 
    S(std::initializer_list<T> l) : v(l)
    {
         std::cout << "以包含 " << l.size() << " 个元素的列表构造\n";
    }
 
    void append(std::initializer_list<T> l)
    {
        v.insert(v.end(), l.begin(), l.end());
    }
 
    std::pair<const T*, std::size_t> c_arr() const
    {
        return {&v[0], v.size()}; // 在 return 语句中进行复制列表初始化
                                  // 没有使用 std::initializer_list
    }
};
 
template<typename T>
void templated_fn(T) {}
 
int main()
{
    S<int> s = {1, 2, 3, 4, 5}; // 复制列表初始化
    s.append({6, 7, 8});        // 在函数调用中进行列表初始化
 
    std::cout << "现在 vector 含有 " << s.c_arr().second << " 个 int:\n";
 
    for (auto n : s.v)
        std::cout << n << ' ';
    std::cout << '\n';
 
    std::cout << "用范围 for 遍历花括号初始化式列表:\n";
 
    for (int x : {-1, -2, -3}) // 对 auto 的规则使得此范围 for 有效
        std::cout << x << ' ';
    std::cout << '\n';
 
    auto al = {10, 11, 12}; // 对 auto 的特殊规则
 
    std::cout << "绑定到 auto 的列表的 size() = " << al.size() << '\n';
    auto la = al; // 顶层代理对象的浅层副本
    assert(la.begin() == al.begin()); // 保证为真:后备数组相同
 
    std::initializer_list<int> il{-3, -2, -1};
    assert(il.begin()[2] == -1); // 注意替代了缺少的 operator[]
    il = al; // 浅复制
    assert(il.begin() == al.begin()); // 保证为真
 
//  templated_fn({1, 2, 3}); // 编译错误!"{1, 2, 3}" 不是表达式,
                             // 它没有类型,所以不能推导出 T
    templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
    templated_fn<std::vector<int>>({1, 2, 3});           // 同样 OK
}

LWG 2129

关于 std::initializer_list 的用户特化。标准中未明确禁止用户对 std::initializer_list 进行特化,这可能导致与标准预期行为不一致。为了解决这个问题,提议在标准中明确说明用户不能显式或部分特化 std::initializer_list,否则会导致编译错误。

下面是一个错误示例,说明用户试图特化 std::initializer_list 会导致问题:

#include <initializer_list>

// 错误:尝试特化 std::initializer_list
template <>
struct std::initializer_list<int> {
    void print() {
        // 假设用户自定义了一个打印函数
        std::cout << "Custom initializer_list<int>" << std::endl;
    }
};

int main() {
    std::initializer_list<int> il = {1, 2, 3};
    // 如果用户成功特化,编译器将不知道如何处理标准行为
    il.print();
    return 0;
}