【问题标题】:C++17 almost uniform initializationC++17 几乎统一初始化
【发布时间】:2018-10-01 19:12:23
【问题描述】:

在本视频的最后(从 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
}

如果我从第一个构造函数中删除显式,它也会使用initializer_list&lt;int&gt; 调用第四个调用

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

【问题讨论】:

    标签: c++ c++17 initializer-list


    【解决方案1】:

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

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

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

    放置 &lt;1, 1&gt; 而不是 &lt;2, 1&gt;。这也是标准容器不支持聚合类型的 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)...};
      }
    }
    

    【讨论】:

    • 这是做什么的? vv.emplace(2,1) 哪个编译器接受这个?
    • @JiveDadson 感谢您的提及。已更正。
    【解决方案2】:

    我需要进行哪些更改才能按照视频中的规则调用 (int) 和 (int, int, int)?

    删除initializer_list&lt;int&gt; 构造函数。这是让它发挥作用的唯一方法。

    是否可以在初始化列表构造函数存在的情况下调用其他构造函数?

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

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

    典型的解决方案是在非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
    }
    

    有什么设计建议可以避免放弃视频中建议的一般规则?

    好吧,考虑到“一般规则”不是一个好规则(他的幻灯片包含典型的反例:尝试用大括号调用vector&lt;int&gt; 的大小+值版本),最好放弃它。关于 auto a{2}; 翻译成的小问题与实际上无法调用某些构造函数无关。

    【讨论】:

    • 我接受了你的回答,但我仍然对一件事感到好奇:将标签放在初始化列表构造函数中并保留其他标签不是更好吗?
    • @GermánDiago:你可以。但这意味着当您想使用初始化列表构造函数时,请执行 {tag, {10, 20, 30}}。这几乎不是惯用的 C++。
    • 是的,但如果常见情况是使用我猜的其他构造函数,则在常见情况下更符合人体工程学。此外,由于重载决议,在猜测何时选择 std::initializer_list 构造函数时存在认知开销:(
    猜你喜欢
    • 2017-07-01
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 2019-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多