You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 21:42:20