从初始化列表初始化,但没有 ... ?

Posted

技术标签:

【中文标题】从初始化列表初始化,但没有 ... ?【英文标题】: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 ... */ 会随着时间的推移在喜欢大括号初始化语法的人中变得相当标准。 【参考方案1】:

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

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

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

C c1;

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

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

【讨论】:

【参考方案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,虽然不是那么简洁,但它提供了更好的灵活性和控制力。

【讨论】:

以上是关于从初始化列表初始化,但没有 ... ?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在初始化列表中没有创建额外副本的情况下分配数据?

C++ - 尽管有括号列表初始化,但不能“新建”未知大小的数组?

如何修复警告:扩展初始化列表?

C++关于初始化列表的细节(必须/不能使用初始化列表的情况初始化列表的效率分析)

C++关于初始化列表的细节(必须/不能使用初始化列表的情况初始化列表的效率分析)

使用初始化列表时如何获取私有数据?