C++中能否判定可调用对象为返回bool的谓词及对应实现
谓词检测的核心逻辑
脱离具体调用参数列表判定“某对象是不是谓词”本身就不具备通用性。谓词的标准定义是可接收指定参数列表、调用返回值可隐式转换为bool的可调用对象,从来不需要存在唯一固定的调用签名。之前的萃取逻辑卡在重载问题上,根源是试图在无参数上下文的前提下,硬抽取可调用对象的固定返回类型,碰到重载函数、带重载operator()的函数对象自然会触发类型推导障碍。
C++17 标准下的可实现方案
核心思路:不要预抽取可调用对象的固定签名,而是在实际调用的参数上下文中,通过SFINAE直接检测调用合法性与返回值类型,天然兼容重载场景。
实现代码
首先实现通用的检测工具:
#include <type_traits> #include <tuple> // 基础SFINAE检测结构 template<typename F, typename Args, typename = void> struct InvokableAsBoolPredicate : std::false_type {}; // 偏特化匹配合法谓词场景 template<typename F, typename... Args> struct InvokableAsBoolPredicate< F, std::tuple<Args...>, std::void_t< // 检测:用Args...调用F是否语法合法 std::enable_if_t<std::is_invocable_v<F, Args...>>, // 检测:调用返回值是否可隐式转换为bool std::enable_if_t<std::is_convertible_v<std::invoke_result_t<F, Args...>, bool>> > > : std::true_type {}; // 便捷调用别名 template<typename F, typename... Args> constexpr bool IsBoolPredicateForArgsV = InvokableAsBoolPredicate<F, std::tuple<Args...>>::value;
嵌入选定的all组合子
auto constexpr all = [](auto const&... predicates){ return [predicates...](auto const&... x){ // 基于实际传入的参数x,逐个校验谓词合法性 static_assert( (IsBoolPredicateForArgsV<decltype(predicates), decltype(x)...> && ...), "传入的谓词不满足要求:请检查1. 谓词元数是否与传入参数匹配;2. 参数类型是否符合谓词要求;3. 谓词是否返回可转换为bool的类型" ); return (predicates(x...) && ...); }; };
关键说明
- 对带重载
operator()的函数对象:SFINAE触发时编译器会自动匹配与传入参数适配的operator()重载版本,无需手动指定具体重载。 - 对重载的普通函数:当处于实际调用参数的上下文时,编译器会自动从重载集合中挑选参数匹配的版本做返回值检测,不会卡在“decltype(重载函数名)无法推导”的问题上。
- 注意:直接将重载普通函数名传入all组合子的外层形参时,会在auto形参的模板推导阶段失败——这是C++语言本身的限制,与检测逻辑无关。如果需要传入重载函数,只需用无捕获lambda包一层即可,无运行时开销:
// 错误:直接传重载函数名,模板推导无法选择重载版本 // all(overloaded_function); // 正确:无捕获lambda包一层,适配推导要求 all([](auto&&... args) -> decltype(auto) { return overloaded_function(std::forward<decltype(args)>(args)...); });
C++20 Concept 方案
C++20的concept机制将上述SFINAE逻辑做了标准化封装,无需手写偏特化结构,代码更简洁、编译错误可读性更好。
实现代码
#include <concepts> // 定义适配任意参数列表的bool谓词concept template<typename F, typename... Args> concept BoolPredicateForArgs = requires(F&& f, Args&&... args) { // 约束:调用合法,且返回值可转换为bool { f(std::forward<Args>(args)...) } -> std::convertible_to<bool>; }; // 重写all组合子 auto constexpr all = [](auto const&... predicates){ return [predicates...](auto const&... x) { static_assert( (BoolPredicateForArgs<decltype(predicates), decltype(x)...> && ...), "传入的谓词不满足要求:请检查1. 谓词元数是否与传入参数匹配;2. 参数类型是否符合谓词要求;3. 谓词是否返回可转换为bool的类型" ); return (predicates(x...) && ...); }; };
方案优势
- 代码量相比C++17的手写SFINAE减少60%以上,逻辑直观无冗余。
- 如果需要将谓词校验直接嵌到函数签名层面做模板约束,concept会直接输出不满足约束的具体原因,错误提示的可读性远优于传统SFINAE。
- 自动适配所有可调用对象类型,包括泛型lambda、重载函数对象、重载普通函数(经lambda包装后)。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

