C++ 在GCC和Clang下,使用lambda复制简单RAII包装的初始化意外失败

C++ 在GCC和Clang下,使用lambda复制简单RAII包装的初始化意外失败,c++,function,c++11,lambda,initialization,C++,Function,C++11,Lambda,Initialization,我在创建一个微不足道的RAII包装时遇到了一个意想不到的问题 更不用说下面代码的逻辑不完整性(复制构造函数和赋值运算符未删除等,这意味着是一个SSCCE),让我印象深刻的是,使用临时lambda对包装进行复制初始化会导致编译错误,而直接初始化则不会 这种行为在GCC 4.7.2和Clang 3.2上都可以观察到,而ICC 13.0.1和VC10编译这两个版本时没有问题 #include <iostream> #include <functional> using nam

我在创建一个微不足道的RAII包装时遇到了一个意想不到的问题

更不用说下面代码的逻辑不完整性(复制构造函数和赋值运算符未删除等,这意味着是一个SSCCE),让我印象深刻的是,使用临时lambda对包装进行复制初始化会导致编译错误,而直接初始化则不会

这种行为在GCC 4.7.2和Clang 3.2上都可以观察到,而ICC 13.0.1和VC10编译这两个版本时没有问题

#include <iostream>
#include <functional>

using namespace std;

struct A
{
    template<typename F>
    A(F&& f) : _f(forward<F>(f)) { }

    ~A() { _f(); }

private:

    std::function<void()> _f;
};

int main()
{
    // A a = [] () { cout << "Hello" << endl; }; // ERROR!
    A a([] () { cout << "Hello" << endl; }); // OK
}
#包括
#包括
使用名称空间std;
结构A
{
模板
A(F&&F):\u F(forward(F)){}
~A(){u f();}
私人:
std::function\u f;
};
int main()
{
//A=[](){cout错误消息(gcc 4.7.2)提供了合理的信息:

c++/4.7/functional: In instantiation of 'static void std::_Function_handler<void(_ArgTypes ...), _Functor>::_M_invoke(const std::_Any_data&, _ArgTypes ...) [with _Functor = A; _ArgTypes = {}]':
c++/4.7/functional:2298:6:   required from 'std::function<_Res(_ArgTypes ...)>::function(_Functor, typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_Useless>::type) [with _Functor = A; _Res = void; _ArgTypes = {}; typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_Useless>::type = std::function<void()>::_Useless]'
source.cpp:9:32:   required from 'A::A(F&&) [with F = A]'
source.cpp:22:44:   required from here
c++/4.7/functional:1926:2: error: no match for call to '(A) ()'
请注意,由于选择了默认的移动构造函数,析构函数应检查
\u f
是否为非空;由于
std::function
的移动构造函数不能保证目标为空,您还应自行执行该更改:

A(A &&a): _f() { std::swap(_f, a._f); }
~A() { if (_f) _f(); }
回想一下(根据8.5p17)副本初始化涉及创建一个prvalue临时值,然后使用该临时值直接初始化目标对象。可以在模板构造函数和隐式定义的副本构造函数之间进行选择;首选使用类型模板参数
a
的模板构造函数,因为
a&&
绑定到prvalue
A
比常数A&

另一种方法(可能更好)是禁用
A
参数的模板构造函数:

template<typename F, typename = typename std::enable_if<!std::is_same<F, A>::value>::type>
A(F&& f) : _f(forward<F>(f)) { }
模板::类型>
A(F&&F):\u F(forward(F)){}
在这种情况下,将选择隐式定义的复制构造函数,因此析构函数不需要检查
\u f
的状态;但是,如果编译器不执行复制省略,那么它(和
\u f
)将被调用两次


允许省略副本(12.8p31);非省略的表单必须是可访问的(12.8p32),但据我所知(通过省略)编译器不需要检查它是否可编译。因此,编译器可以编译或拒绝编译程序;但是,如果它确实编译,则它必须执行了复制省略。

您应该在编译器中发布错误消息,因为这些消息可以帮助理解编译器不喜欢的内容。我不这样认为e拷贝初始化不起作用的任何原因…@DavidRodríguez dribeas:对。让我来做。哈。如果我们实现operator(),代码将被编译。斯科特·迈尔斯的这篇最新文章可能有任何用处?顺便说一句,他称
模板为A(F&&)
一个通用复制构造函数,而不是移动构造函数。@rhalbersma:谢谢你的链接,它似乎很相关。我记得读过这篇文章,但我有点忘记了,我肯定要再仔细阅读一遍。那么VC10和ICC 13.0.1中是否有错误?我不明白为什么该构造函数应该抑制复制或移动e-constructor。顺便说一句,为什么应该选择移动构造函数而不是我的构造函数模板?@GManNickG:是用户定义的析构函数抑制了它。但是我仍然不明白为什么选择移动构造函数而不是构造函数模板。@ecatmur:没问题,但是为什么仍然需要移动构造函数?我想你的声称
std::function
不可复制是不正确的。而且每个类中都有相同的旧复制构造函数隐式可用,我不知道非常量签名来自何处。
A(A &&a): _f() { std::swap(_f, a._f); }
~A() { if (_f) _f(); }
template<typename F, typename = typename std::enable_if<!std::is_same<F, A>::value>::type>
A(F&& f) : _f(forward<F>(f)) { }