6

与えられた:

#include <functional>
class world_building_gun;
class tile_bounding_box;
typedef std::function<void (world_building_gun, tile_bounding_box)> worldgen_function_t;
void foo() {
    worldgen_function_t v;
    worldgen_function_t w(v);
}

これをコンパイルする必要がありますか?私のコンパイラは言う:

はい:GCC / stdlibc ++(また、boost :: functionはGCCとClangの両方でyesです)

いいえ:Clang / libc ++(http://libcxx.llvm.org/、Clang 3.0、今日のlibc ++ SVN)

(「いいえ」が正解の場合は、実際のコードを修正して、完全な型をより多くのヘッダーに配置するか、boost :: functionを使用します。)

編集:これがClangエラーメッセージです:

In file included from foo.cpp:2:
In file included from /usr/include/c++/v1/functional:462:
/usr/include/c++/v1/type_traits:2766:19: error: invalid appli
    static_assert(sizeof(_Tp) > 0, "Type must be complete.");
                  ^~~~~~~~~~~
/usr/include/c++/v1/type_traits:2752:15: note: in instantiation of template class 'std::__1::__check_complete<world_buildin
    : private __check_complete<_Hp>,
              ^
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<world_buildin
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2919:15: note: in instantiation of template class 'std::__1::__check_complete<std::__1::fun
      world_building_gun, tile_bounding_box>' requested here
    : private __check_complete<_Fp, _Args...>
              ^
/usr/include/c++/v1/type_traits:2930:11: note: in instantiation of template class 'std::__1::__invokable_imp<std::__1::func
      world_building_gun, tile_bounding_box>' requested here
          __invokable_imp<_Fp, _Args...>::value>
          ^
/usr/include/c++/v1/functional:1115:33: note: in instantiation of template class 'std::__1::__invokable<std::__1::function<
      world_building_gun, tile_bounding_box>' requested here
    template <class _Fp, bool = __invokable<_Fp&, _ArgTypes...>::value>
                                ^
/usr/include/c++/v1/functional:1141:35: note: in instantiation of default argument for '__callable<std::__1::function<void (world_building_gun, tile_bounding_box)> >' required here
              typename enable_if<__callable<_Fp>::value>::type* = 0);
                                  ^~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1140:7: note: while substituting deduced template arguments into function template 'function' [with _Fp = std::__1::function<void
      (world_building_gun, tile_bounding_box)>]
      function(_Fp,
      ^
foo.cpp:4:7: note: forward declaration of 'world_building_gun'
class world_building_gun;
      ^
In file included from foo.cpp:2:
In file included from /usr/include/c++/v1/functional:462:
/usr/include/c++/v1/type_traits:2766:19: error: invalid application of 'sizeof' to an incomplete type 'tile_bounding_box'
    static_assert(sizeof(_Tp) > 0, "Type must be complete.");
                  ^~~~~~~~~~~
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<tile_bounding_box>' requested here
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class 'std::__1::__check_complete<world_building_gun, tile_bounding_box>' requested here
      private __check_complete<_T0, _Tp...>
              ^
/usr/include/c++/v1/type_traits:2919:15: note: in instantiation of template class 'std::__1::__check_complete<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
    : private __check_complete<_Fp, _Args...>
              ^
/usr/include/c++/v1/type_traits:2930:11: note: in instantiation of template class 'std::__1::__invokable_imp<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
          __invokable_imp<_Fp, _Args...>::value>
          ^
/usr/include/c++/v1/functional:1115:33: note: in instantiation of template class 'std::__1::__invokable<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
      world_building_gun, tile_bounding_box>' requested here
    template <class _Fp, bool = __invokable<_Fp&, _ArgTypes...>::value>
                                ^
/usr/include/c++/v1/functional:1141:35: note: in instantiation of default argument for '__callable<std::__1::function<void (world_building_gun, tile_bounding_box)> >' required here
              typename enable_if<__callable<_Fp>::value>::type* = 0);
                                  ^~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1140:7: note: while substituting deduced template arguments into function template 'function' [with _Fp = std::__1::function<void
      (world_building_gun, tile_bounding_box)>]
      function(_Fp,
      ^
foo.cpp:5:7: note: forward declaration of 'tile_bounding_box'
class tile_bounding_box;
      ^
2 errors generated.

「worldgen_function_tw(v);」という行を削除すると、Clang +libc++は正常にコンパイルされます。または、クラスを完全な型にした場合。

4

3 に答える 3

4

この例がコンパイルされるように libc++ に修正をコミットし、リビジョン 160285 をコミットしました。

私は、libc++ が完全な型の引数リストをチェックすることに過度に積極的だったと思います。

于 2012-07-16T16:25:31.843 に答える
4

編集: どうやら、この問題は現在修正されているため、以下のテキストは履歴として見ることができます。:)


問題は確かに(私が予測したように)テンプレート化されたctorでのlibc ++のSFINAEチェックにあります(理由については、この質問を確認してください)。次の(たとえば)が有効かどうかを確認し、内部の奥深くではなく、建設現場で適切でクリーンなエラーを返します( libstd ++またはMSVCstd::functionで次の例を試してください... shudder):

#include <functional>

void f(int* p){}

int main(){
  std::function<void(int)> fun(f);
}

void (*)(int*)libc++ は、適用可能なもの (テンプレート化された ctor) のみが SFINAE を取得するため、「引数リストに一致するコンストラクターが見つかりません」という行に沿ってコンパイラーに何かを吐き出させます。

ただし、__callableand__invoke_impチェックが機能するには、引数と戻り値の型が完全である必要があります。そうしないと、ここでは暗黙的な変換が考慮されないためです。

テンプレート化された ctor も調べられる理由は、ベスト マッチ (この場合はコピー ctor) を検討する前にすべての ctor が列挙されるためです。


std::function現在、標準では、呼び出し可能なオブジェクトからオブジェクトを構築するとき (別名、テンプレート化された ctor を呼び出すとき) に、引数と戻り値の型が完全である必要があることが非常に明確になっています。

§20.8.11.2.1 [func.wrap.func.con] p7

template <class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

必須: Fである必要がありますCopyConstructible。引数の型と戻り値の型に対してfCallable (20.8.11.2) である必要があります。[...]ArgTypesR

(注: 「必須」は、実装者ではなく、機能のユーザーを対象としています。)

§20.8.11.2 [func.wrap.func] p2

型の callable オブジェクトfは、未評価のオペランド (第 5 節) と見なされる expression が整形式 (20.8.2)である場合、引数の型Fと戻り値の型に対してCallableです。ArgTypesRINVOKE(f, declval<ArgTypes>()..., R)

§20.8.2 [func.req]

p1 次のように定義します。INVOKE(f, t1, t2, ..., tN)

  • (t1.*f)(t2, ..., tN)fクラスのメンバ関数へのポインタでありTt1Tのオブジェクト、型のオブジェクトへのT参照、または から派生した型のオブジェクトへの参照である場合T
  • ((*t1).*f)(t2, ..., tN)whenはクラスのfメンバー関数へのポインターであり、前の項目で説明した型のいずれでもない。Tt1
  • [...]
  • f(t1, t2, ..., tN)他のすべての場合。

p2に暗黙的に変換されるように定義します。INVOKE(f, t1, t2, ..., tN, R)INVOKE(f, t1, t2, ..., tN)R

したがって、libc++ はテンプレート化された ctor で SFINAE チェックを行う権利を確実に持っています。型が完全である必要があるためです。ただし、実際の SFINAE チェックがまったく必要ない場合でも、完全な型の安全性チェックがトリガーされることは、少し残念であり、欠陥と見なされる場合があります (コピー ctor が常に呼び出されるため)。callableこれは、次のようにチェックを遅延させることで軽減できます。

template<bool Copy, class F>
struct lazy_callable{
  static bool const value = callable<F>::value;
};

template<class F>
struct lazy_callable<true, F>{
  static bool const value = false;
};

template<class F>
function(F f, typename enable_if<lazy_callable<!std::is_same<F,function>::value>::type* = 0);

これは、実際にそうでないcallable場合にのみ SFINAE チェックをトリガーする必要があります。Fstd::function<...>

うーん、最後にちょっと脱線したかも…

于 2012-05-24T05:45:30.007 に答える
3

I'd say no. From 20.8.11.2 Class template function [func.wrap.func], we have:

3 The function class template is a call wrapper (20.8.1) whose call signature (20.8.1) is R(ArgTypes...).

In 20.8.1 Definitions [func.def ], we get the following definitions as to what constitutes a call wrapper type, a call wrapper and a call signature:

2 A call signature is the name of a return type followed by a parenthesized comma-separated list of zero or more argument types.

5 A call wrapper type is a type that holds a callable object and supports a call operation that forwards to that object.

6 A call wrapper is an object of a call wrapper type.

Notice how paragraph 2 doesn't mention completeness of types.

To cut a story short (a lot of definitions are involved), the meaning of 'callable object' here means either a functor (the familiar notion, i.e. something that can be used like a function) or a pointer-to-member. Furthermore the Standard also describe the Callable concept in 20.8.11.2 paragraph 2:

A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE(f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.8.2).

(The INVOKE bit is an imaginary function that the Standard uses to define how functors and pointers to members are, well, invoked.)

What I think is the most important conclusion from that is the following requirement:

  • given a callable object that is Callable with signature R(A...), then R and A... are complete (or possibly R is void) by virtue of the INVOKE expression (i.e. otherwise it would not be well-formed, notice the use of declval<ArgTypes>()...)

My argument now rests on '[the] call operation that forwards to that object' in the definition of call wrappers, which I think is intentionally left vague so as to not be overly restrictive. In the case of std::function<Sig> where some incomplete types are involved in Sig then we could define this operation as being 'first complete the types, then treat std::function<Sig> as a callable object type of call signature Sig'.

Given that, here are the key points of my argument:

  • std::function is not described as being a callable object or as being Callable for any signature
  • invoking an std::function is done in terms of INVOKE (20.8.11.2.4 function invocation [func.wrap.func.inv])
  • constructing an std::function from a callable object is in terms of Callable with the call signature of std::function (20.8.11.2.1 function construct/copy/destroy [func.wrap.func.con] paragraph 7)
  • calling the target member of std::function is in terms of Callable with the call signature of std::function (20.8.11.2.5 function target access [func.wrap.func.targ])
  • all other operations of std::function are not described in terms of callable object(*) , Callable, INVOKE or otherwise require that the call signature of std::function involve complete types

(*) except in the case of one constructor where the description contains "shall not throw exceptions if f’s target is a callable object passed via reference_wrapper or a function pointer". I think in the context it's clear that this doesn't affect the argument. For what it's worth this constructor is not involved in the snippet of the OP.

So I'd say unless you do use one of those operations that indirectly require the signature to involve complete types, you're good to go.


It's all well and good to analyze what the Standard prescribes but it's also important to consider what is the intent of the Standard. In this case I think it is very much desirable and expected that std::function does not require that the types of the call signature be complete. Consider the following:

// in a_fwd.hpp
struct incomplete;
using callback_type = std::function<void(incomplete)>;
callback_type make_callback();

// in b.hpp; depends on a_fwd.hpp
#include "a_fwd.hpp"
void eat_callback(callback_type);

Then without the requirement an unrelated TU, let's call it C, that is a client of B can do:

// in c.cpp
#include "b.hpp"

// somewhere in e.g. a function body
eat_callback(make_callback());

This is type-safe and minimizes the coupling since only translation unit B need to know about the details of translation unit A.

Furthermore both Boost.Function and libstdc++ have demonstrated that it is possible to implement std::function without such a requirement.

于 2012-05-24T06:06:15.277 に答える