std :: function copy-constructor是否要求模板类型参数类型为完整类型?

时间:2012-05-24 03:17:22

标签: c++ boost c++11 tr1

假设:

#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 ++(在GCC和Clang中也是boost :: function都是)

否:Clang / libc ++(http://libcxx.llvm.org/,Clang 3.0,libc ++ SVN截至今天)

(如果“no”是正确答案,我将修复我的真实代码,将完整类型放入更多标题或使用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_t w(v);”行,

Clang + libc ++编译成功或者如果我使类完成类型。

3 个答案:

答案 0 :(得分:4)

修改: Apperently,此问题现已修复,因此以下文字可视为历史记录。 :)


问题确实(正如我预测的那样)使用libc ++的SFINAE检查模板化的ctor(为了推理,检查this question)。它会检查以下(例如)是否有效并在构造站点上提供一个漂亮而干净的错误,而不是在std::function的内部深处(尝试使用{{3的以下示例)或MSVC ... 颤抖):

#include <functional>

void f(int* p){}

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

libc ++将导致编译器按照“找不到与参数列表void (*)(int*)匹配的构造函数”的方式吐出某些内容,因为唯一适用的一个(模板化的ctor)会将SFINAE输出。

但是,为了使__callable__invoke_imp检查有效,参数和返回类型需要完成,否则这里不会考虑隐式转换。

模板化ctor的原因是,在考虑最佳匹配(在这种情况下是复制文件)之前,所有ctors都被列举。


现在,标准非常清楚,当从可调用对象(也称为模板化ctor)构造std::function对象时,需要完成参数和返回类型:

§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。对于参数类型f ArgTypes应为Callable (20.8.11.2),并返回类型R。 [...]

(注意:“需要”解决了功能的用户,而不是实施者。)

§20.8.11.2 [func.wrap.func] p2

  

f类型的可调用对象F对于参数类型ArgTypes Callable ,如果表达式 {则返回类型R {1}} INVOKE被视为未评估的操作数(第5条),格式良好(20.8.2)。

(f, declval<ArgTypes>()..., R)

  

p1定义 §20.8.2 [func.req] INVOKE,如下所示:

     
      
  • (f, t1, t2, ..., tN)(t1.*f)(t2, ..., tN)是指向类f的成员函数的指针时,Tt1类型的对象或对{类型为T的对象或对从T;
  • 派生的类型的对象的引用   
  • T((*t1).*f)(t2, ..., tN)是指向类f的成员函数的指针时,T不是上一项中描述的类型之一;
  •   
  • [...]
  •   在所有其他情况下
  • t1
  •   
     

p2将 f(t1, t2, ..., tN) INVOKE定义为 (f, t1, t2, ..., tN, R) INVOKE隐式转换为(f, t1, t2, ..., tN)。< / p>

因此,libc ++当然有权在模板化的ctor中进行SFINAE检查,因为类型需要完整,否则你会得到未定义的行为。但是,它可能有点不幸并被认为是一个缺陷,即即使不需要实际的SFINAE检查,也会触发完整类型的安全检查(因为将始终调用复制程序)。这可以通过使R检查一个懒惰的方式来缓解,例如

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,则应仅触发F SFINAE检查。

男人,我可能最后在这里做了一点......

答案 1 :(得分:4)

我已经修改了libc ++,这个例子现在编译,提交修订版160285.

我相信libc ++在检查完整类型的参数列表时过于激进。

答案 2 :(得分:3)

我会说不。从20.8.11.2类模板函数[func.wrap.func]开始,我们有:

  

3 function类模板是一个调用包装器(20.8.1),其调用签名(20.8.1)为R(ArgTypes...)

在20.8.1定义[func.def]中,我们得到以下关于构成调用包装器类型,调用包装器和调用签名的定义:

  

2调用签名是返回类型的名称,后跟带有括号的逗号分隔的零个或多个参数类型列表。

     

5调用包装器类型是一种类型,它包含一个可调用对象并支持转发到该对象的调用操作。

     

6调用包装器是调用包装器类型的对象。

请注意第2段未提及类型的完整性。

为了简化故事(涉及很多定义),“可调用对象”的含义在这里意味着一个仿函数(熟悉的概念,即可以像函数一样使用的东西)或者指针指向会员。此外,标准还描述了20.8.11.2第2段中的 Callable 概念:

  

f类型的可调用对象F对于参数类型ArgTypes Callable ,如果表达式 INVOKE,则返回类型R (f, declval<ArgTypes>()..., R),被视为未评估的操作数(第5条),形成良好(20.8.2)。

INVOKE 位是一个虚函数,标准用它来定义如何调用成员的函子和指针。)

我认为最重要的结论是以下要求:

  • 给定一个具有签名R(A...) Callable 的可调用对象,然后RA...完成(或R void {{} 1}})凭借 INVOKE 表达式(即,否则它的格式不正确,请注意使用declval<ArgTypes>()...

我的论点现在依赖于调用包装器定义中的“转发到该对象的调用操作”,我认为这是故意保持模糊,以免过度限制。对于std::function<Sig>Sig涉及某些不完整类型的情况,我们可以将此操作定义为“首先完成类型,然后std::function<Sig>视为可调用对象类型的调用签名Sig'。

鉴于此,以下是我的论点的关键点:

  • std::function未被描述为可调用对象或任何签名 Callable
  • 根据 INVOKE (20.8.11.2.4函数调用[func.wrap.func.inv])调用std::function
  • 从可调用对象构造std::function Callable ,调用签名为std::function(20.8.11.2.1函数构造/复制/销毁[func] .wrap.func.con]第7段)
  • 调用target的{​​{1}}成员是 Callable ,调用签名为std::function(20.8.11.2.5函数目标访问权限[func] .wrap.func.targ])
  • std::function的所有其他操作均未根据可调用对象(*),可调用 INVOKE 或以其他方式要求{的调用签名来描述{1}}涉及完整类型

(*)除了在一个构造函数的情况下,其中描述包含“如果std::function的目标是通过std::function传递的可调用对象或函数指针”,则不应抛出异常。我认为在上下文中很明显这不会影响论证。对于它的价值,这个构造函数不参与OP的片段。

所以我要说除非你确实使用间接要求签名涉及完整类型的那些操作之一,否则你很高兴。


分析标准规定的内容既好又好,但考虑标准的意图也很重要。在这种情况下,我认为非常需要并期望f不要求呼叫签名的类型完整。请考虑以下事项:

reference_wrapper

然后没有要求不相关的TU,我们称之为C,即B的客户可以这样做:

std::function

这是类型安全的并且最小化耦合,因为只有翻译单元B需要知道翻译单元A的细节。

此外,Boost.Function和libstdc ++都证明了没有这样的要求就可以实现// 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);