如何在C++11/C++14中实现std::regular_invocable类型特性?
背景
我正在开发一个Arduino“GameBoy”风格项目,要制作类游戏引擎的库。使用AVR板时Arduino IDE没有C++ STL,所以需要自行实现STL中的容器和类型特性组件。目前需要把std::regular_invocable实现为元函数——因为Arduino IDE基于C++14,不支持concepts。但std::regular_invocable依赖requires表达式隐式保证等式保持,实现起来遇到了困难。我已经完成了std::is_invocable和std::invoke的实现。
已实现的std::invocable代码
namespace __detail { struct __do_invocable_concept_impl { template<class _Fn, class... _Args, class = decltype(invoke(forward<_Fn>(declval<_Fn&&>()), forward<_Args>(declval<_Args&&>())...))> static true_type __test(int); template<class, class> static false_type __test(...); }; template<class _Fn, class... _Args> struct __invocable_concept_impl : public __do_invocable_concept_impl { using type = decltype(__test<_Fn, _Args...>(0)); }; } template<class _Fn, class... _Args> struct invocable : public __detail::__invocable_concept_impl<_Fn, _Args...>::type { };
libstdc++中的std::invocable和std::regular_invocable实现
template<class _Fn, class... _Args> concept invocable = requires(_Fn&& __f, _Args&&... __args) { invoke(forward<_Fn>(__f), forward<_Args>(__args)...); }; template<class _Fn, class... _Args> concept regular_invocable = invocable<_Fn, _Args...>; // 此实现自动隐含等式保持的语义要求
问题
我对新版C的requires表达式不太熟悉,仅了解它能创建未初始化变量用于函数调用和比较。想问:不依赖requires表达式的情况下,如何在C14中检查可调用类型及其实参在调用后是否保持等式?
我曾尝试用declval结合bool_constant检查等式,但无法比较传入invoke的_Fn&&类型——因为declval处于未求值上下文。我需要在编译时检查_Fn类型变量和_Args...中的变量在调用后是否仍相等。
解决方案
首先要明确:C14没有requires表达式的话,无法完全模拟concepts对regular_invocable的等式保持检查——因为regular_invocable的等式保持是语义要求,而非编译期可直接验证的语法约束。libstdc里的regular_invocable看起来只是继承invocable,但实际上它的等式保持是隐含的语义规则,requires表达式并没有做语法层面的强制检查。
不过可以在编译期做语法层面的近似模拟,尽可能贴近语义要求:
1. 实现思路
- 先复用已有的
invocable确保可调用性合法; - 对左值参数检查其是否支持相等比较(因为右值参数调用后不需要保持等式,通常会被移动消耗);
- 模拟调用前后左值参数的可比较性是否依然成立。
2. 代码实现
#include <type_traits> using namespace std; // 辅助trait:检查类型是否支持相等比较 namespace __detail { template<class T, class = void> struct is_equal_comparable : false_type {}; template<class T> struct is_equal_comparable<T, decltype(void(declval<T&>() == declval<T&>()))> : true_type {}; // 复用已有的invocable trait template<class _Fn, class... _Args> using is_invocable_t = typename invocable<_Fn, _Args...>::type; // 测试左值参数调用后的可比较性保持 template<class _Fn, class... _Args, typename = enable_if_t<is_invocable_t<_Fn, _Args&...>::value>> static true_type __test_regular_lvalue(int) { // 验证调用后所有左值参数仍可相等比较 using check_eq = bool_constant<(is_equal_comparable<_Args>::value && ...)>; return check_eq{}; } template<class, class...> static false_type __test_regular_lvalue(...); // 核心实现逻辑 template<class _Fn, class... _Args> struct __regular_invocable_impl { static constexpr bool base_invocable = is_invocable_t<_Fn, _Args...>::value; // 左值参数检查 + 基础可调用性检查 static constexpr bool value = base_invocable && decltype(__test_regular_lvalue<_Fn, _Args...>(0))::value; }; } // 对外暴露的regular_invocable trait template<class _Fn, class... _Args> struct regular_invocable : bool_constant<__detail::__regular_invocable_impl<_Fn, _Args...>::value> {}; template<class _Fn, class... _Args> constexpr bool regular_invocable_v = regular_invocable<_Fn, _Args...>::value;
3. 局限性说明
- 仅能检查语法层面的可比较性,无法验证语义上的等式保持(比如调用函数后参数的值是否真的未改变)——这在C++14中无法通过编译期元编程实现;
- 对右值参数不做检查,符合STL
regular_invocable的语义(右值本身不要求调用后保持状态); - 如果需要检查可调用对象本身(
_Fn)的左值调用后等式保持,可以扩展代码,将_Fn&加入参数列表进行同等检查。
内容的提问来源于stack exchange,提问作者Andrey Stroitelev

