从初始化列表初始化,但没有 ... ?
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 <typename T> Foo<T>::Foo(const T&)
这样的东西吗?或者类似template <typename T1> template <typename T2> Foo<T1>::Foo(T2&&)
?【参考方案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<>
可以同时包含map
和int
。我的问题中的代码是一个示例,它不是我要解决的最终问题。
好的,一组模板参数包转发构造函数如何,为联合中的每个成员一个,并使用enable_if
确保启用适当的一个(并且只有一个)?我不知道确切的语法,但我会尝试...
更新这个答案会很好,现在语法已经广为人知了。
@bames53:请随意。
看起来完美转发实际上在这里不起作用。我添加了一个带有解释的答案。【参考方案6】:
我同意这很丑。一旦我超越了非常简单的情况,我通常会废弃初始化列表,因为它们非常有限。对于您的情况,我会选择 boost::assign,虽然不是那么简洁,但它提供了更好的灵活性和控制力。
【讨论】:
以上是关于从初始化列表初始化,但没有 ... ?的主要内容,如果未能解决你的问题,请参考以下文章
C++ - 尽管有括号列表初始化,但不能“新建”未知大小的数组?
C++关于初始化列表的细节(必须/不能使用初始化列表的情况初始化列表的效率分析)