【问题标题】:Initializing from an initializer list, but without {{{{{{{{ ... }}}}}}}}?从初始化列表初始化,但没有 {{{{{{{{ ... }}}}}}}}?
【发布时间】:2011-08-09 16:22:11
【问题描述】:

我最近偶然发现了初始化列表的一些问题。考虑一个存储类似地图的数据的程序

struct MyMapLike {
  MyMapLike(std::map<std::string, int> data)
    :data(std::move(data))
  { }

private:
  std::map<std::string, int> data;
};

这看起来很简单。但是当初始化它时,它变得丑陋。我想让它看起来像

MyMapLike maps = { { "One", 1 }, { "Two", 2 } };

但是编译器不想接受这个,因为上面的意思是它应该寻找一个可以分别接受{ "One", 1 }{ "Two", 2 }的双参数构造函数。我需要添加额外的大括号,使其看起来像一个接受{ { ... }, { ... } }的单参数构造函数@

MyMapLike maps = { { { "One", 1 }, { "Two", 2 } } };

我不想那样写。由于我有一个类似地图的类,并且初始化程序具有映射列表的抽象值,因此我想使用以前的版本,并且独立于任何此类实现细节,例如构造函数的嵌套级别。

一种解决方法是声明一个初始化列表构造函数

struct MyMapLike {
  MyMapLike(std::initializer_list< 
    std::map<std::string, int>::value_type
    > vals)
    :data(vals.begin(), vals.end())
  { }

  MyMapLike(std::map<std::string, int> data)
    :data(std::move(data))
  { }

private:
  std::map<std::string, int> data;
};

现在我可以使用前者,因为当我有一个初始化列表构造函数时,整个初始化列表被视为一个元素,而不是被拆分为多个元素。但我认为构造函数的这种单独需求非常丑陋。

我正在寻求指导:

  • 您如何看待前一种和后一种形式的初始化?在这种情况下需要额外的大括号是否有意义?
  • 您认为在这种情况下添加初始化列表构造函数的要求不好吗?

如果你同意我的观点,前一种初始化方式更好,你能想到什么解决方案?

【问题讨论】:

  • @MooingDuck,我认为他就是这么做的!请参阅::value_type
  • “额外”的外部{} 的作用与内部的不同:它们是大括号初始化语法的一部分,表示正在调用构造函数,但 不是传递给构造函数的实际对象的一部分。同时,内部大括号指示映射的初始化列表的实际开始。我认为这是完全有道理的,事实上,Clang 警告最外层大括号的某些(合法)省略,所以我预计 {{ /* ... stuff ... */ }} 会随着时间的推移在喜欢大括号初始化语法的人中变得相当标准。

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


【解决方案1】:

您如何看待前一种和后一种形式的初始化?在这种情况下需要额外的大括号是否有意义?

我想是的。我认为允许一个构造函数不仅在语法匹配该构造函数时被调用,而且当语法与构造函数的单个参数的某个构造函数匹配时,会允许太多歧义,以此类推。

struct A { int i; };
struct B { B(A) {}  B(int) {} };
struct C { C(B) {} };

C c{1};

您认为在这种情况下添加初始化列表构造函数的要求很糟糕吗?

没有。它可以让你得到你想要的语法,但不会产生如果我们让编译器更难搜索构造函数时出现的问题。

【讨论】:

    【解决方案2】:

    你可以如下初始化:

    MyMapLike maps ( { { "One", 1 }, { "Two", 2 } } );
    

    外部的( ... ) 现在显然只是简单地包围了构造函数参数,并且更容易看出最外部的{ ... } 定义了“列表”。

    还是有点冗长,但是避免了{被用来做两件不同的事情,影响可读性的情况。

    【讨论】:

      【解决方案3】:

      您如何看待前一种和后一种形式的初始化?在这种情况下需要额外的大括号是否有意义?

      我更喜欢第一种形式,因为它有一个干净的类接口。采用初始化列表的构造函数会污染接口并且提供很少的回报。

      额外的大括号是我们会习惯的。就像我们习惯了 C++ 的其他怪癖一样。

      【讨论】:

      • 当你得到正确的初始化列表时,我认为不需要额外的大括号。
      【解决方案4】:

      由于我有一个类似映射的类,并且初始化器具有映射列表的抽象值,所以我想使用以前的版本

      问题就在这里:由 提供允许你的 类被视为地图的构造函数。你称你的解决方案是一种变通方法,但没有什么可以变通的。 :)

      但我认为构造函数的这种单独需要非常丑陋。

      它是,但不幸的是,因为它是你的类,你必须指定初始化列表的工作方式。

      【讨论】:

      • 虽然有时我不知道。当我有一个泛型类的构造函数采用T 时,我不知道如何为它编写初始化列表构造函数:(
      • @JohannesSchaub-litb 我不确定你所说的构造函数采用T 是什么意思——你的意思是像template &lt;typename T&gt; Foo&lt;T&gt;::Foo(const T&amp;) 这样的东西吗?或者类似template &lt;typename T1&gt; template &lt;typename T2&gt; Foo&lt;T1&gt;::Foo(T2&amp;&amp;)
      【解决方案5】:

      这样的东西不会产生预期的效果(MyMapLike 可以以std::map 可以的任何方式构造,但不会隐式转换为std::map)?

      struct MyMapLike : private std::map<std::string, int>
      {
          using map::map;
      };
      

      如果绝对肯定必须是成员,则可以使用构造函数完美转发(我不确定确切的语法),如下所示:

      struct MyMapLike
      {
          template<typename... Initializers>
          MyMapLike(Initializers... init, decltype(new std::map<std::string, int>(...init)) = 0)
            : data(...init)
          { }
      
      private:
          std::map<std::string, int> data;
      };
      

      【讨论】:

      • 是的,但不能选择继承。例如,我考虑有一个variant&lt;&gt; 可以同时包含mapint。我的问题中的代码是一个示例,它不是我要解决的最终问题。
      • 好的,一组模板参数包转发构造函数如何,为联合中的每个成员一个,并使用enable_if 确保启用适当的一个(并且只有一个)?我不知道确切的语法,但我会尝试...
      • 更新这个答案会很好,现在语法已经广为人知了。
      • @bames53:请随意。
      • 看起来完美转发实际上在这里不起作用。我添加了一个带有解释的答案。
      【解决方案6】:

      我同意这很丑。一旦我超越了非常简单的情况,我通常会废弃初始化列表,因为它们非常有限。对于您的情况,我会选择 boost::assign,虽然不是那么简洁,但它提供了更好的灵活性和控制力。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-06-05
        • 1970-01-01
        • 2016-09-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-05
        • 1970-01-01
        相关资源
        最近更新 更多