4

単純な RAII ラッパーを作成しているときに、予期しない問題に遭遇しました。

以下のコードの論理的な不完全さ (コピー コンストラクターと代入演算子が削除されていないなど、これは SSCCE を意味する) は言うまでもなく、一時的なラムダを使用してラッパーをコピー初期化するとコンパイル エラーが発生することに驚かされます。 、直接初期化はしません。

この動作は 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
}

誰が正しくて、間違っている人には何が問題なのですか? C++ 標準ライブラリの実装の問題ですか、それともコンパイラの問題ですか?

C++11 標準への参照は特に歓迎されます。

編集:

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 エラーが発生しました。

4

1 に答える 1

3

エラー メッセージ (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) ()'

問題は、コピー初期化で使用できる暗黙的な移動コンストラクターがクラスにないことです。Aユーザー定義のデストラクタ (12.8p9b4) があるため、暗黙的に定義されたムーブ コンストラクタは削除されます。

追加:

A(A &&) = default;

_fデフォルトの移動コンストラクタが選択されているため、デストラクタはそれが空でないことを確認する必要があることに注意してください。の移動コンストラクターはstd::function、ターゲットが空のままであることを保証しないため、その変更も自分で実行する必要があります。

A(A &&a): _f() { std::swap(_f, a._f); }
~A() { if (_f) _f(); }

(8.5p17 に従って) コピーの初期化には、一時的な prvalue の作成が含まれることを思い出してください。これは、ターゲット オブジェクトを直接初期化するために使用されます。選択肢は、テンプレート コンストラクターと暗黙的に定義されたコピー コンストラクターの間です。type template 引数を持つテンプレート コンストラクターAが優先A &&されAますconst A &

別の (おそらくより良い) 方法は、A引数のテンプレート コンストラクターを無効にすることです。

template<typename F, typename = typename std::enable_if<!std::is_same<F, A>::value>::type>
A(F&& f) : _f(forward<F>(f)) { }

この場合、暗黙的に定義されたコピー コンストラクタが選択されるため、デストラクタは の状態をチェックする必要はありません_f。ただし、コンパイラがコピー省略を実行しない場合は、それ (および_f) が 2 回呼び出されます。

コピー省略が許可されています (12.8p31)。省略されていないフォームはアクセス可能である必要があります(12.8p32) が、私が知る限り (および省略により)、コンパイラはそれがコンパイル可能であることを確認する必要はありません。したがって、コンパイラがプログラムをコンパイルすることも、コンパイルを拒否することも許されます。ただし、コンパイルされる場合は、コピー省略が実行されている必要があります。

于 2013-02-07T17:05:51.737 に答える