C++17 几乎一致的初始化

C++17 almost uniform initialization

在本视频的结尾(从 15:57 开始)有关于如何在 C++17 中使用几乎一致的初始化的建议:video here

要点是这样的:始终使用直接初始化 auto a{...};MyType a{...}; 不要为您的类型使用复制初始化 = {...}

#include <iostream>


struct MyType {
    explicit MyType(std::initializer_list<int>) {
        std::cout << "Called std::initializer_list<int>" << std::endl;
    }

    explicit MyType(int) {
        std::cout << "Called int." << std::endl;
    }

    MyType(int, int, int) {
        std::cout << "Called int, int, int" << std::endl;
    }
};



int main() {
    MyType calls_init_list{10}; //Calls initializer_list<int>
    MyType calls_init_list_2{10, 20}; //Calls initializer_list<int>
    MyType calls_init_list_3{10, 20, 30}; //Calls initializer_list<int>

    MyType compile_error = {10, 20, 30}; //Compile error
}

如果我从第一个构造函数中删除 explicit ,它将调用第 4 次调用 initializer_list<int>

  1. 我需要做哪些更改才能按照视频中的规则调用 (int)(int, int, int)
  2. 在存在初始化列表构造函数的情况下,甚至可以调用其他构造函数吗?
  3. 有什么设计建议可以避免放弃视频中建议的一般规则吗?终于有一些有意义的东西会很好,C++ 初始化可能是其中最糟糕的部分。

在您的情况下,要调用 MyType(int, int, int)explicit MyType(int),您必须使用 () 语法而不是 {}

基本上,我不认为总是使用 {} 语法是个好主意。例如,从 C++17 开始,标准库中的所有 emplace 方法都在内部使用 () 而不是 {}。例如,代码

std::vector<std::vector<int>> vv;
vv.emplace_back(2, 1);

取代 <1, 1> 而不是 <2, 1>。这也是标准容器不支持聚合类型的 emplace 构造的原因。

在我看来,您可以坚持的真正统一初始化是在可能的情况下执行 () 初始化,否则回退到 {} 的初始化(例如,对于聚合类型)。另见 this。可能的实现:

template <typename...>
struct paren_initable: std::false_type {};

template <typename T, typename... Us>
struct paren_initable<decltype((void)T(std::declval<Us>()...)), T, Us...>
 : std::true_type {};

template <typename T, typename... Us>
inline constexpr bool paren_initable_v = paren_initable<void, T, Us...>::value;

template <typename T, typename... Us>
T emplace(Us&&... us) {
  if constexpr (paren_initable_v<T, Us...>) {
    return T(std::forward<Us>(us)...);
  }
  else {
    return T{std::forward<Us>(us)...};
  }
}

What changes should I need for being able to call (int) and (int, int, int) following the rule in the video?

删除 initializer_list<int> 构造函数。这是让它发挥作用的唯一方法。

Is it even possible to call the other constructors in the presence of the initializer list constructor?

是的,只要花括号初始化列表中的类型不能匹配任何initializer_list<T>构造函数中的类型。他们总是具有首要地位。

因此它被嘲笑为“几乎统一初始化”。

典型的解决方案是向非initializer_list构造函数添加一些标记类型:

struct tag_t {};
constexpr inline tag_t tag;

struct MyType {
    explicit MyType(std::initializer_list<int>) {
        std::cout << "Called std::initializer_list<int>" << std::endl;
    }

    MyType(tag_t, int) {
        std::cout << "Called int." << std::endl;
    }

    MyType(tag_t, int, int, int) {
        std::cout << "Called int, int, int" << std::endl;
    }
};

int main() {
    MyType three_int = {tag, 10, 20, 30}; //Calls 3-`int` constructor
}

any design recommendations to avoid abandoning the general rule adviced in the video?

嗯,考虑到 "general rule" 不是一个好的规则(他的幻灯片包含典型的反例:尝试用大括号调用 vector<int> 的大小+值版本),这样更好放弃它。关于 auto a{2}; 翻译成什么的小问题与字面上无法调用某些构造函数无关。