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

否: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_t w(v);”行,Clang+libc++ 编译成功 或者如果我让课程完成类型。

4

3 回答 3

4

编辑: 显然,这个问题现在已经解决了,所以下面的文字可以看作是历史。:)


这个问题确实(正如我所预测的那样)是 libc++ 在模板化 ctor 中的 SFINAE 检查(为了推理,请检查这个问题)。它检查以下(例如)是否有效,并在施工现场而不是在内部深处给出一个漂亮而干净的错误(尝试使用libstd++或 MSVCstd::function的以下示例......不寒而栗):

#include <functional>

void f(int* p){}

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

libc++ 将导致编译器按照“没有找到与参数列表匹配的构造函数”的行吐出一些东西void (*)(int*),因为唯一适用的(模板化的 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);

要求: FCopyConstructible。对于参数类型和返回类型f应为 Callable (20.8.11.2) 。[...]ArgTypesR

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

§20.8.11.2 [func.wrap.func] p2

如果表达式被视为未计算的操作数(第 5 条),格式正确(20.8.2) ,则f类型的可调用对象对于参数类型和返回类型F都是可调用的。ArgTypesRINVOKE(f, declval<ArgTypes>()..., R)

§20.8.2 [func.req]

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

  • (t1.*f)(t2, ..., tN)whenf是指向类的成员函数的指针,T并且t1是类型对象T或对类型对象的T引用或对派生于的类型对象的引用T
  • ((*t1).*f)(t2, ..., tN)whenf是指向类的成员函数的指针,T并且t1不是上一项中描述的类型之一;
  • [...]
  • 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);

这应该只触发callableSFINAE 检查是否F实际上不是std::function<...>

伙计,最后我可能跑题了...

于 2012-05-24T05:45:30.007 回答
4

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

I believe libc++ was being overly aggressive in checking the argument list for complete types.

于 2012-07-16T16:25:31.843 回答
3

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

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

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

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

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

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

注意第 2 段没有提到类型的完整性。

简而言之(涉及很多定义),这里的“可调用对象”的含义是指函子(熟悉的概念,即可以像函数一样使用的东西)或指向成员的指针。此外,该标准还在20.8.11.2 第 2 段中描述了Callable概念:

如果表达式INVOKE被视为未计算的操作数(第 5 条),格式正确(20.8.2),则f类型的可调用对象对于参数类型和返回类型F都是可调用的。ArgTypesR(f, declval<ArgTypes>()..., R)

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

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

  • 给定一个可调用的对象,它是Callable with signature R(A...),然后RA...是完整的(或可能Rvoid)凭借INVOKE表达式(即否则它不会是格式良好的,请注意使用declval<ArgTypes>()...

我的论点现在依赖于调用包装器定义中的“[the] call operation that forwards to that object”,我认为它是故意含糊不清的,以免过度限制。在std::function<Sig>涉及一些不完整类型的情况下,Sig我们可以将此操作定义为“首先完成类型,然后std::function<Sig>其视为调用签名的可调用对象类型Sig”。

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

  • std::function未被描述为可调用对象或可调用任何签名
  • 调用 anstd::function是根据 INVOKE 完成的 20.8.11.2.4 函数调用 [func.wrap.func.inv])
  • std::function从可调用对象构造一个可调用对象的调用签名为std::function(20.8.11.2.1 函数构造/复制/销毁 [func.wrap.func.con] 第 7 段)
  • 根据Callable调用target成员,调用签名为(20.8.11.2.5 function target access [func.wrap.func.targ])std::functionstd::function
  • 的所有其他操作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无需这样的要求也可以实现。

于 2012-05-24T06:06:15.277 回答