使用lambda的简单RAII包装器的复制初始化在GCC和Clang下意外地失败 [英] Copy-initialization of simple RAII wrapper with lambda fails unexpectedly under GCC and Clang
问题描述
我在创建一个简单的RAII包装器时遇到了一个意想不到的问题。
更不用说下面的代码的逻辑不完整性(复制构造函数和赋值运算符没有删除等,这意味着是一个SSCCE),什么打击我是
没有。p>这个行为可以在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 }; //错误!
A a([](){cout<<Hello<< endl;}); // OK
}
谁是对的是谁的错误的问题?是C ++标准库的实现问题,还是编译器问题?
特别欢迎参考C ++ 11标准。
编辑:
以下是Clang 3.2所产生的错误:
完成时出现错误:
从source.cpp中包含的文件中:2:
/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../ ../include/c++/4.7/functional:1925:2:错误:类型'A'不提供调用操作符
(* _Base :: _ M_get_pointer(__ functor))(
^ ~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/4.7/ ../../../../include/c++/4.7/functional:2297:33:注意:在实例化成员函数'std :: _ Function_handler< void(),A> :: _ M_invoke'请求这里
_M_invoker =& _My_handler :: _ M_invoke;
^
source.cpp:9:16:注意:在实例化函数模板专门化'std :: function< void()> ::函数< A>请求这里
A(F& f):_f(forward< F(f)){}
^
source.cpp:20:7:note :在函数模板专用化的实例化中,'A :: A< A&'请求这里
A a = [](){cout< Hello<< endl }; //错误!
^
产生1个错误。
错误信息(gcc 4.7.2)是合理的信息:
c ++ / 4.7 / functional:在实例化'static void std :: _ Function_handler< void(_ArgTypes ...),_Functor> :: _ M_invoke(const std :: _ Any_data& _ArgTypes ...)[with _Functor = _ArgTypes = {}]':
c ++ / 4.7 / functional:2298:6:需要从'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:需要从'A :: A(F&& amp;)[with F = A]'
source.cpp:22:44 :需要从这里
c ++ / 4.7 /功能:1926:2:错误:没有匹配调用'(A)()'
问题是你的类没有可用于复制初始化的隐式移动构造函数。隐式定义的移动构造函数被删除,因为
A
有一个用户定义的析构函数(12.8p9b4)。
Add :
A(A&&请注意,由于默认的移动构造函数被选中,析构函数应该检查
不为空;由于
< _fstd :: function
的移动构造函数不能保证目标为空,因此您也应该自己执行该更改:A(A& a):_f(){std :: swap(_f,a._f); }
〜A(){if(_f)_f(); }
记住(每8.5p17)一个复制初始化包括创建一个prvalue临时然后用于直接初始化目标对象。选择是在模板构造函数和隐式定义的拷贝构造函数之间;优选具有类型模板参数
A
的模板构造函数,因为A&&&
绑定到prvalue <$ c $
另一种方法是(可能的话)。
更好)是禁用 A
参数的模板构造函数:template< ; typename F,typename = typename std :: enable_if<!std :: is_same< F,A> :: value> :: type>
A(F& f):_f(forward&F(f)){}
$ b b在这种情况下,将选择隐式定义的复制构造函数,因此析构函数不需要检查
_f
的状态;然而,如果编译器不执行复制elision,则它(和_f
)将被调用两次。
允许偏差(12.8p31);非消隐形式必须可以访问(12.8p32),但是只要我能告诉(和省略)编译器不需要检查它是否可编译。因此,允许编译器编译或拒绝编译程序;如果它编译,虽然,它必须执行复制elision。
I've come across an unexpected problem while creating a trivial RAII wrapper.
Let alone the logical incompleteness of the code below (copy-constructor and assignment operator not deleted etc., this is meant to be an SSCCE), what strikes me is that copy-initialization of my wrapper with a temporary lambda results in a compilation error, while direct-initialization does not.
This behavior can be observed both on GCC 4.7.2 and on Clang 3.2, while ICC 13.0.1 and VC10 compile both versions without problems.
#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 }
Who is right, and what is the problem with those who are wrong? Is it an issue with the implementation of the C++ Standard Library, or rather a compiler issue?
References to the C++11 Standard are particularly welcome.
EDIT:
Here is the error produced by Clang 3.2:
Compilation finished with errors: In file included from source.cpp:2: /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/functional:1925:2: error: type 'A' does not provide a call operator (*_Base::_M_get_pointer(__functor))( ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../include/c++/4.7/functional:2297:33: note: in instantiation of member function 'std::_Function_handler<void (), A>::_M_invoke' requested here _M_invoker = &_My_handler::_M_invoke; ^ source.cpp:9:16: note: in instantiation of function template specialization 'std::function<void ()>::function<A>' requested here A(F&& f) : _f(forward<F>(f)) { } ^ source.cpp:20:7: note: in instantiation of function template specialization 'A::A<A>' requested here A a = [] () { cout << "Hello" << endl; }; // ERROR! ^
1 error generated.
解决方案The error message (gcc 4.7.2) is reasonably informative:
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) ()'
The problem is that your class does not have an implicit move constructor available for use in copy-initialization. The implicitly defined move constructor is deleted, since
A
has a user-defined destructor (12.8p9b4).Add:
A(A &&) = default;
Note that since the defaulted move constructor is selected, the destructor should check that
_f
is non-empty; since the move constructor ofstd::function
does not guarantee that the target is left empty, you should also perform that change yourself:A(A &&a): _f() { std::swap(_f, a._f); } ~A() { if (_f) _f(); }
Recall that (per 8.5p17) a copy-initialization involves the creation of a prvalue temporary, which is then used to direct-initialize the target object. The choice is between the template constructor and the implicitly defined copy constructor; the template constructor with type template argument
A
is preferred, sinceA &&
binds to prvalueA
better thanconst A &
does.An alternative (possibly better) is to disable the template constructor for
A
arguments:template<typename F, typename = typename std::enable_if<!std::is_same<F, A>::value>::type> A(F&& f) : _f(forward<F>(f)) { }
In this case the implicitly defined copy constructor will be selected, so the destructor doesn't need to check the state of
_f
; however if the compiler does not perform copy elision then it (and_f
) will be called twice.Copy-elision is allowed (12.8p31); the non-elided form must be accessible (12.8p32) but as far as I can tell (and by omission) the compiler is not required to check that it is compilable. So it is permissible for a compiler to either compile or refuse to compile the program; if it does compile, though, it must have performed copy elision.
这篇关于使用lambda的简单RAII包装器的复制初始化在GCC和Clang下意外地失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!