std::函数的复制构造函数是否要求模板类型的参数类型是完整的类型
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是肯定的)
否: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++ 编译成功。
编辑:当然,这个问题现在已经修复,所以下面的文本可以看作是历史。 :)
<小时 />问题确实(正如我所预测的那样)与模板化 ctor 中的 libc++ 的 SFINAE 检查有关(出于推理,请检查此问题)。它检查以下内容(例如)是否有效,并在施工现场给出一个漂亮而干净的错误,而不是在std::function
的深处(尝试以下示例使用 libstd++ 或 MSVC...不寒而栗):
#include <functional>
void f(int* p){}
int main(){
std::function<void(int)> fun(f);
}
libc++ 将导致编译器吐出类似"找不到与参数列表匹配的构造函数"之类的内容void (*)(int*)
,因为唯一适用的构造函数(模板化 ctor)会得到 SFINAE'd。
但是,为了使__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
应为可调用 (20.8.11.2)。[...]
(注意:"要求"针对功能的用户,而不是实现者。
§20.8.11.2 [func.wrap.func] p2
如果表达式
INVOKE
(f, declval<ArgTypes>()..., R)
(被视为未计算的操作数(子句 5))格式正确 (20.8.2),则类型为F
的可调用f
对象对于参数类型ArgTypes
和返回类型R
是可调用的。
§20.8.2 [func.req]
p1 定义
INVOKE
(f, t1, t2, ..., tN)
如下:
(t1.*f)(t2, ..., tN)
当f
是指向类T
的成员函数的指针,并且t1
是类型T
的对象或对类型T
对象的引用或对派生自T
的类型对象的引用;((*t1).*f)(t2, ..., tN)
当f
是指向类T
的成员函数的指针,并且t1
不是上一项中描述的类型之一时;- [...]
f(t1, t2, ..., tN)
在所有其他情况下。p2 将
INVOKE
(f, t1, t2, ..., tN, R)
定义为隐式转换为R
INVOKE
(f, t1, t2, ..., tN)
。
因此,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 检查,如果实际上没有std::function<...>
F
。
伙计,我最后可能有点跑题了...
我已经提交了对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段中描述了可赎回的概念:
如果表达式 INVOKE
(f, declval<ArgTypes>()..., R)
(被视为未计算的操作数(子句 5))格式正确 (20.8.2),则类型为F
的可调用f
对象对于参数类型ArgTypes
和返回类型R
是可调用的。
(INVOKE 位是一个虚构函数,标准使用它来定义如何调用函子和指向成员的指针。
我认为最重要的结论是以下要求:
- 给定一个带有签名
R(A...)
的可调用对象,则R
和A...
是完整的(或者可能R
void
),借助 INVOKE 表达式(即否则它的格式不正确,请注意使用declval<ArgTypes>()...
)
我现在的论点基于调用包装器定义中的"转发到该对象的调用操作",我认为这是故意模糊的,以免过度限制。在Sig
涉及一些不完整类型的std::function<Sig>
的情况下,我们可以将此操作定义为"首先完成类型,然后将std::function<Sig>
视为调用签名的可调用对象类型Sig
"。
鉴于此,以下是我论点的关键点:
-
std::function
未被描述为可调用对象或可调用的任何签名 - 调用
std::function
是根据 INVOKE 完成的(20.8.11.2.4 函数调用 [func.wrap.func.inv]) - 从可调用对象构造
std::function
是 Callable 的,调用签名为std::function
(20.8.11.2.1 函数构造/复制/销毁 [func.wrap.func.con] 第 7 段) - 调用
std::function
的target
成员是 Callable,调用签名为std::function
(20.8.11.2.5 函数目标访问 [func.wrap.func.targ]) std::function
的所有其他操作都不用可调用对象(*)、可调用、调用或以其他方式要求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
。
- 在 c++ 中的模板实例化中使用带有构造函数的类作为类型参数
- 如何解决一元"*"(有"字符")错误的无效类型参数?
- "std::shared_ptr":不是参数"_Ty"的有效模板类型参数
- 具有可变参数非类型参数的模板专用化
- 函数类型参数的模板参数推导
- PowerShell 使用结构类型参数调用 C++ DLL 的导出函数
- 对于非常量指针类型的参数,未调用具有常量指针模板类型参数的功能
- 为模板传递非类型参数 agument
- 为什么带有类型参数的运算符 () 可以应用于 result_of 上下文中的类型?
- 使用其他模板类型参数作为要在函数签名中使用的类型别名声明
- 如何避免具有相同类型参数的函数中的错误
- 将内置类型变量传递给只有一个类类型参数的"+"运算符函数时自动类型转换的构造函数
- c++非类型参数包扩展
- 如何实现对参数顺序不可知的std::same_as的广义形式(即对于两个以上的类型参数)
- 在不同的模板参数包之间分发非类型参数包
- 如何在使用容器和字符串时强制使用显式分配器类型参数
- 错误:一元"*"的类型参数无效(具有"int"):使用 mergesort 计算
- EXPECT_CALL具有 unique_ptr 引用类型参数的模拟函数
- 作为模板类型参数,为什么 type[N] 与其专用版本不匹配----模板<类 T>类 S<T[]>
- C++ 模板:重载时找不到基类类型参数方法