问题 无法使用统一初始化复制std :: vector >。它是否正确?


以下代码无法在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。


6849
2017-12-30 01:46


起源



答案:


这是 LWG 2132 这还不是一个缺陷报告,但有明确的共识(和实施经验)来解决它。标准说 std::function的构造函数将接受任何类型,因为初始化列表构造函数总是优先于其他构造函数,如果它是可行的,你的代码会尝试构造一个向量 std::initializer_list<std::function<void()>> 使用从对象初始化的单个元素 a。那会导致错误,因为虽然你可以构造一个 std::function<void()> 从 a 生成的对象不可调用。

换句话说,问题是 std::function 有一个无约束的模板构造函数允许从任何类型转换。这会导致您的案例出现问题,因为如果可行,则初始化列表构造函数优先于其他构造函数,并且不受约束 function 构造函数意味着它总是可以创建一个 initializer_list<function<void()>> 从任何类型,所以初始化列表构造函数总是可行的。

拟议的2132号决议阻止了构建 std::function 从一个不可调用的类型,所以初始化列表构造函数是不可行的 vector 而是调用复制构造函数。 我为GCC 4.8实施了该决议,它已经在Clang的libc ++库中实现了。


11
2017-12-30 12:29



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


答案:


这是 LWG 2132 这还不是一个缺陷报告,但有明确的共识(和实施经验)来解决它。标准说 std::function的构造函数将接受任何类型,因为初始化列表构造函数总是优先于其他构造函数,如果它是可行的,你的代码会尝试构造一个向量 std::initializer_list<std::function<void()>> 使用从对象初始化的单个元素 a。那会导致错误,因为虽然你可以构造一个 std::function<void()> 从 a 生成的对象不可调用。

换句话说,问题是 std::function 有一个无约束的模板构造函数允许从任何类型转换。这会导致您的案例出现问题,因为如果可行,则初始化列表构造函数优先于其他构造函数,并且不受约束 function 构造函数意味着它总是可以创建一个 initializer_list<function<void()>> 从任何类型,所以初始化列表构造函数总是可行的。

拟议的2132号决议阻止了构建 std::function 从一个不可调用的类型,所以初始化列表构造函数是不可行的 vector 而是调用复制构造函数。 我为GCC 4.8实施了该决议,它已经在Clang的libc ++库中实现了。


11
2017-12-30 12:29



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


我看不出为什么这不应该编译,gcc(版本4.8.0 20121111)和clang(版本3.3(trunk 171007))编译代码。也就是说,“统一初始化”远非统一:在调用构造函数时,肯定会出现无法使用大括号的情况。


5
2017-12-30 01:58



g ++ 4.7.2(4.7.2-5ubuntu1)不编译代码。很奇怪的编译错误信息: pastebin.com/b1mcbYRq - leemes
@leemes,因为问题打开“以下代码不能在GCC 4.7.2或Clang 3.2中编译:”,我假设OP知道这一点(并且可能是首先提出问题的原因)。 - WhozCraig
@WhozCraig是的,但他没有提供编译器消息。这就是我首先发表此评论的原因。 - leemes