无法使用统一初始化对std::vector<std::function<void ()>>进行复制,这正确吗?

11
以下代码在GCC 4.7.2或Clang 3.2中无法编译:
#include <vector>
#include <functional>

int main()
{
   std::vector<std::function<void()>> a;
   std::vector<std::function<void()>> b{a};
}
问题在于编译器会尝试使用 initializer_list 创建 b,而显然它应该只调用复制构造函数。然而,这似乎是期望的行为,因为标准规定 initializer_list 构造函数应优先考虑。
对于其他 std::vector,此代码将正常工作,但对于 std::function,编译器无法知道您是否要使用 initializer_list 构造函数或其他构造函数。
看起来没有绕过它的方法,如果是这种情况,那么您永远无法在模板代码中使用统一初始化,这将是非常遗憾的事情。
另一方面,Visual Studio(2012 年 11 月 CTP 版)不会抱怨这一点。但目前那里的 initializer_list 支持并不是很好,所以可能是一个 bug。
2个回答

11
这是LWG 2132,目前还不是一个缺陷报告,但已经有明确的共识(和实现经验)来解决它。标准规定std::function的构造函数将接受任何类型,因此,如果初始化列表构造函数可行,它总是优先于其他构造函数。您的代码尝试使用从对象a初始化的单个元素构造std::initializer_list<std::function<void()>>中的向量,这将导致错误,因为虽然可以从a构造std::function<void()>,但生成的对象无法调用。
换句话说,问题在于std::function具有无约束的模板构造函数,允许从任何类型进行转换。这在您的情况下会引起问题,因为如果可行,初始化列表构造函数优先于其他构造函数,并且无约束的function构造函数意味着始终可以从任何类型创建initializer_list<function<void()>>,因此初始化列表构造函数始终可行。

提议的2132决议防止从非可调用类型构造std::function,因此初始化列表构造函数不可行,而调用了vector复制构造函数。 我已经在GCC 4.8中实现了这个决议,并且在Clang的libc++库中也已经实现。


感谢您的回答。LWG 2132链接实际上讨论了一个类似但不同的问题,恰好也解决了这个问题。这意味着该问题只会为std::function修复,而不是为具有模板构造函数的其他类型修复。我认为我的新统一初始化规则是“在任何可以使用它的地方都要使用它,除非对象具有initializer_list构造函数。然后只有在您想要该构造函数时才使用它。”这也意味着您不能在模板代码中使用它。 - Malte Skarupke
我更喜欢遵循“不编写无限制的构造函数模板,允许从任何类型进行隐式转换”的规则。如果您不创建这样的类型,则不会为尝试使用具有初始化列表构造函数的类与您的类型的人们造成问题。标准库没有遵循这个规则,但正在为std::function进行修复。 - Jonathan Wakely

5

我看不到任何理由为什么这段代码不能编译,而且gcc(版本4.8.0 20121111)和clang(版本3.3 (trunk 171007))都可以编译该代码。 但是,“统一初始化”远非统一:在调用构造函数时,有明确的情况下你不能使用大括号。


g++ 4.7.2 (4.7.2-5ubuntu1)无法编译该代码。非常奇怪的编译器错误信息:http://pastebin.com/b1mcbYRq - leemes
1
@leemes,由于问题开头是“以下代码在GCC 4.7.2或Clang 3.2中无法编译:”,我认为提问者已经知道这一点(很可能是问题产生的原因)。 - WhozCraig
@WhozCraig 是的,但他没有提供编译器信息。这就是我首先发布这个评论的原因。 - leemes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接