【问题标题】:Does std::function's copy-constructor require the template type's argument types to be complete types?std::function 的复制构造函数是否要求模板类型的参数类型是完整类型?
【发布时间】:2012-05-30 15:14:46
【问题描述】:

给定:

#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++ 编译成功或者如果我让类完成类型。

【问题讨论】:

  • Clang 只说“不”?我敢打赌它提供了某种错误输出。可以发一下吗?
  • 你能用 libstdc++ 试试 Clang 吗?这可能是 libc++ 的 SFINAE 检查的副作用,它尝试查看传递给其模板化 ctor 的任何内容是否实际上可以使用模板参数中的参数类型调用。这都是未经评估的上下文,但它会执行decltype(declval&lt;F&gt;()(declval&lt;Args&gt;()...)) 之类的操作,这可能会导致此问题。从实现 POV 来看,我认为这段代码没有理由不编译。
  • @mfontanini 感谢上帝,编译器不只是说“不”
  • 由于bugs.archlinux.org/task/29397,我现在无法使用 libstdc++ 尝试 Clang,但是 Clang+Boost.Function (#include , use boost::function) 成功编译了这个(所以这里的关键因素可能是库,而不是编译器)。
  • 你的编译器告诉你应该编译什么?

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


【解决方案1】:

编辑: Apperently,这个问题现在已经修复,所以下面的文字可以被视为历史。 :)


问题确实(正如我所预测的那样)是 libc++ 在模板化 ctor 中的 SFINAE 检查(出于某种原因,请检查 this question)。它检查以下(例如)是否有效,并在 construction 站点而不是在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 和返回类型 Rf 应该是可调用的 (20.8.11.2)。 [...]

(注意:“Requires”指的是功能的用户,而不是实现者。)

§20.8.11.2 [func.wrap.func] p2

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

§20.8.2 [func.req]

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

  • (t1.*f)(t2, ..., tN)f 是指向类T 的成员函数的指针并且t1T 类型的对象或对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

因此,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 检查F 实际上不是std::function&lt;...&gt;

伙计,我可能在最后有点离题了......

【讨论】:

  • 我不认为这与懒惰有关。由于F 是构造函数的模板参数,因此可调用检查应该是依赖的,并且在实例化std::function&lt;Sig&gt; 时不会导致硬错误。这可能是 Clang 在 SFINAE 方面的限制而不是 libc++ 中的缺陷吗?
  • 注:当 F 不可调用时,我更改了 libstdc++ 以禁用构造函数(这是LWG 2132 的建议解决方案),因此 G++ 4.8 会说 no matching function for call to 'std::function: :function(void (&)(int*))' 用于您的示例,但仍然可以编译 OP 的示例而不会出错
  • @JonathanWakely:很有趣。检查过程中参数和返回类型需要完整的问题如何处理?我与 Luc Danton 进行了一次交谈并得出结论,该标准未指定模板是否会在重载解析期间实例化,如果它没有在最后调用(这里就是这种情况) .或者这是 GCC 和 Clang 的重载解决方案之间的区别?
  • 我没有做任何特别的处理,所以它可能是编译器前端的差异
【解决方案2】:

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

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

【讨论】:

  • 注意评论我的回答 w.r.t.完整性检查?这样一个懒惰的检查不应该有帮助,并且仍然允许完整性检查,这样用户就不会得到未定义的行为(如果我正确理解了“必需”部分的话)。
  • 我认为,正如您的回答所建议的那样,将这个构造函数禁用为复制构造函数(同样用于赋值)是一个好主意。我刚刚签入了 libc++ 的代码来做到这一点。谢谢!
【解决方案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 和返回类型R可调用,如果表达式INVOKE(f, declval&lt;ArgTypes&gt;()..., R),被视为未计算的操作数(第 5 条),格式正确 (20.8.2)。

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

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

  • 给定一个 Callable 带有签名 R(A...) 的可调用对象,那么 RA... 是完整的(或者可能 Rvoid),因为 INVOKE 表达式(即否则格式不正确,注意使用declval&lt;ArgTypes&gt;()...

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

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

  • std::function 未被描述为可调用对象或对于任何签名可调用
  • 调用std::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 段)
  • 调用std::functiontarget 成员是在Callable 方面,调用签名为std::function(20.8.11.2.5 函数目标访问[func.wrap.func.targ ])
  • std::function 的所有其他操作均未以 callable object(*)、CallableINVOKE 的形式描述,否则需要 std::function 的调用签名涉及完整类型

(*) 除非一个构造函数的描述包含“如果f 的目标是通过reference_wrapper 传递的可调用对象或函数指针,则不应抛出异常”。我认为在上下文中很明显这不会影响论点。值得一提的是,这个构造函数不参与 OP 的 sn-p。

所以我想说,除非您确实使用其中一种间接要求签名涉及完整类型的操作,否则您就可以开始了。


分析标准的规定固然很好,但考虑标准的意图是什么也很重要。在这种情况下,我认为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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多