繁体   English   中英

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

[英]Does std::function's copy-constructor require the template type's argument types to be complete types?

鉴于:

#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是yes)

否: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 ++编译成功。 或者如果我将类设置为完整类型。

编辑: 显然 ,此问题已修复,因此以下文本可以视为历史记录。 :)


这个问题确实是(正如我所预测的)libc ++的SFINAE在模板化的ctor中进行检查(出于某种原因,请检查此问题 )。 它会检查以下(例如)是有效的,并给出在施工现场一个非常干净的错误,而不是深部的内脏里std::function (尝试用下面的例子libstd ++或MSVC ... 不寒而栗 ):

#include <functional>

void f(int* p){}

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

libc ++将导致编译器按照“找不到与参数列表void (*)(int*)匹配的构造函数”的方式吐出某些东西,因为唯一适用的变​​量(模板化ctor)被SFINAE淘汰了。

但是,为了使__callable__invoke_imp检查工作正常,参数和返回类型必须完整,因为在此否则将不考虑隐式转换。

甚至查看模板化ctor的原因是,在考虑最佳匹配之前(在这种情况下为复制ctor),已枚举了所有ctor。


现在,该标准非常清楚,从可调用对象(也称为调用模板化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 对于参数类型ArgTypes和返回类型R f应为Callable (20.8.11.2)。 [...]

(注意:“要求”针对的是功能的用户 ,而不是实现者。)

§20.8.11.2 [func.wrap.func] p2

一个可调用对象f的类型F为参数类型是可赎回 ArgTypes和返回类型R如果表达式INVOKE (f, declval<ArgTypes>()..., R)视为未计算的操作数(第5章),是公形成 (20.8.2)。

§20.8.2 [func.req]

p1如下定义INVOKE (f, t1, t2, ..., tN)

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

p2将INVOKE (f, t1, t2, ..., tN, R) 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);

如果F实际上不是std::function<...>则这仅应触发callable SFINAE检查。

伙计,我可能最后在这里离题了...

我已经提交了对libc ++的修复,以便此示例现在可以进行编译,提交的修订版为160285。

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

我会说不。 从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概念:

如果表达式INVOKE (f, declval<ArgTypes>()..., R) F类型的可调用对象f 调用为参数类型ArgTypes并返回类型R (20.8.2)。

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

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

  • 给定一个具有签名R(A...) Callable对象,则借助于INVOKE表达式, RA...是完整的(或者R可能是void )(即否则格式不正确,注意使用declval<ArgTypes>()...

我的论点现在基于调用包装的定义中的“转发到该对象的调用操作”,我认为这是故意含糊的,以免过于严格。 在的情况下std::function<Sig>其中一些不完全类型参与Sig然后我们可以定义这个操作为“先完成类型, 那么治疗std::function<Sig>呼叫签名的一个可调用对象类型Sig '。

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

  • std::function未描述为可调用对象或用于任何签名
  • 调用std::function是根据INVOKE进行的 (20.8.11.2.4函数调用[func.wrap.func.inv])
  • 从可调用对象构造std::function方式是带有std::function的调用签名的Callable (20.8.11.2.1函数construct / copy / destroy [func.wrap.func.con]第7段)
  • 调用std::functiontarget成员是带有std::function的调用签名的Callable (20.8.11.2.5函数目标访问[func.wrap.func.targ])
  • std::function所有其他操作未按照callable object(*), CallableINVOKE进行描述,否则要求std::function的调用签名涉及完整类型

(*),除非在一个构造函数中描述包含“如果f的目标是通过reference_wrapper或函数指针传递的可调用对象,则不应引发异常”。 在上下文中,我认为这显然不会影响论点。 值得的是,此构造函数不包含在OP的代码段中。

因此,我要说的是,除非您确实使用间接需要签名包含完整类型的那些操作之一,否则您还是可以的。


分析该标准的规定是一件好事,但考虑该标准的意图也很重要。 在这种情况下,我认为非常期望并且期望std::function不需要呼叫签名的类型完整。 考虑以下:

// 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);

然后,不需要一个不相关的TU,我们将其称为C,即B的客户可以做到:

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

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

这是类型安全的,并且由于只有翻译单元B才需要了解翻译单元A的详细信息,因此可以最大程度地减少耦合。

此外,Boost.Function和libstdc ++都证明可以实现std::function而无需这样的要求。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM