C++20如何从谓词参数推导concept约束的模板参数T
C++20 从谓词自动推导模板参数T实现方案
问题根因
原代码编译失败的核心原因是模板参数T处于非推导语境:std::predicate<T>仅约束P1必须满足可接受T类型参数的谓词要求,但编译器不会反向从P1的调用签名中推导T的具体类型,因此必须手动显式指定T。
实现逻辑
要实现自动推导T,只需要提前从传入的谓词(lambda、函数指针、仿函数)的调用签名中,提取第一个入参的裸类型作为T即可,全程保留concept约束做类型检查,不需要用户手动传参。
第一步:实现可调用对象参数类型提取trait
通过模板特化匹配所有常见可调用对象的签名,提取第一个入参类型,同时用std::remove_cvref_t去掉const、引用、volatile限定,避免类型不匹配:
#include<type_traits> template<class F> struct first_arg_type; // 匹配普通函数、函数指针 template<class R, class Arg, class... Args> struct first_arg_type<R(*)(Arg, Args...)> { using type = std::remove_cvref_t<Arg>; }; // 匹配const限定的lambda、仿函数operator() template<class F, class R, class Arg, class... Args> struct first_arg_type<R(F::*)(Arg, Args...) const> { using type = std::remove_cvref_t<Arg>; }; // 匹配非const的仿函数operator() template<class F, class R, class Arg, class... Args> struct first_arg_type<R(F::*)(Arg, Args...)> { using type = std::remove_cvref_t<Arg>; }; template<class F> using first_arg_type_t = typename first_arg_type<F>::type;
第二步:修改工厂函数/添加CTAD指引
两种用法二选一即可:
- 保留原
make_foo接口:去掉模板参数列表里的T,直接从传入的P1提取T,加上concept约束
template<class P1> requires std::predicate<P1, first_arg_type_t<std::remove_cvref_t<P1>>> auto make_foo(P1 &&f) { using T = first_arg_type_t<std::remove_cvref_t<P1>>; return Foo<T, P1>(std::forward<P1>(f)); }
- 更简洁的CTAD写法:给Foo加类模板推导指引,直接构造对象不需要工厂函数
// 原Foo类定义完全不需要修改 template<class T, std::predicate<T> P1> struct Foo { P1 _f; Foo(P1 &&f): _f(std::forward<P1>(f)) {} }; // 加这一行推导指引即可 template<class P1> Foo(P1&&) -> Foo<first_arg_type_t<std::remove_cvref_t<P1>>, P1>;
完整可运行代码
#include<concepts> #include<utility> #include<string> #include<type_traits> template<class F> struct first_arg_type; template<class R, class Arg, class... Args> struct first_arg_type<R(*)(Arg, Args...)> { using type = std::remove_cvref_t<Arg>; }; template<class F, class R, class Arg, class... Args> struct first_arg_type<R(F::*)(Arg, Args...) const> { using type = std::remove_cvref_t<Arg>; }; template<class F, class R, class Arg, class... Args> struct first_arg_type<R(F::*)(Arg, Args...)> { using type = std::remove_cvref_t<Arg>; }; template<class F> using first_arg_type_t = typename first_arg_type<F>::type; template<class T, std::predicate<T> P1> struct Foo { P1 _f; Foo(P1 &&f): _f(std::forward<P1>(f)) {} }; // make_foo版本 template<class P1> requires std::predicate<P1, first_arg_type_t<std::remove_cvref_t<P1>>> auto make_foo(P1 &&f) { using T = first_arg_type_t<std::remove_cvref_t<P1>>; return Foo<T, P1>(std::forward<P1>(f)); } // CTAD指引版本(可选) template<class P1> Foo(P1&&) -> Foo<first_arg_type_t<std::remove_cvref_t<P1>>, P1>; int main() { auto fun = [](const std::string &s){return s == "toto";}; // 两种写法均无需显式指定T,可直接编译通过 auto my_foo_make = make_foo(fun); auto my_foo_ctad = Foo(fun); return 0; }
注意事项
- 上述实现兼容GCC10+、Clang13+、VS2022 17.1+等所有支持C++20标准的主流编译器
- 如果你的谓词需要使用非第一个位置的参数作为T,只需要调整trait的特化逻辑,提取对应位置的参数类型即可
- 所有类型检查依然由
std::predicateconcept完成,不会丢失C++20 concepts的类型安全优势
内容的提问来源于stack exchange,提问作者WaterFox
相关产品推荐
相关产品推荐

