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

C++多参数Concept作为函数约束的类型推导问题探讨

仿射空间Concept的模板推导问题与解决

一、先定义基础Concept

首先把标量约束为任意算术类型:

template<class T>
concept scalar = std::is_arithmetic_v<T>;

接着定义基于标量ScalarType的向量概念,要求支持加减、数乘、取反等运算:

template<class T, class ScalarType>
concept vector = std::equality_comparable<T> && scalar<ScalarType> && requires(T a, T b, ScalarType c)
{
    {a + b} -> std::same_as<T>;
    {a - b} -> std::same_as<T>;
    {a += b} -> std::same_as<T&>;
    {a -= b} -> std::same_as<T&>;
    {c * a} -> std::same_as<T>;
    {a * c} -> std::same_as<T>;
    {a / c} -> std::same_as<T>;
    {a *= c} -> std::same_as<T&>;
    {a /= c} -> std::same_as<T&>;
    {-a} -> std::same_as<T>;
    {zero(std::declval<empty<T>>())} -> std::same_as<T>;
};

最后定义点概念,要求点能和向量做加减,两点相减得到向量:

template<class T, class VectorType, class ScalarType>
concept point = std::equality_comparable<T>
    && vector<VectorType, ScalarType>
    && requires(T p1, T p2, VectorType v)
{
    {p1 + v} -> std::same_as<T>;
    {p1 - v} -> std::same_as<T>;
    {p1 += v} -> std::same_as<T&>;
    {p1 -= v} -> std::same_as<T&>;
    {p1 - p2} -> std::same_as<VectorType>;
};

二、距离函数的推导坑

用point约束写了个两点距离计算函数:

template<class Vector, class Scalar, point<Vector, Scalar> P>
constexpr auto distance(P p1, P p2)
{
    return norm(p1 - p2);
}

结果发现只传点类型P的时候,编译器推不出Vector和Scalar参数。比如非void指针符合point概念:

static_assert(point<int*, intptr_t, intptr_t>);

调用时直接报错:

constexpr std::array<int, 20> test{};
auto d = ::distance(std::data(test), std::data(test) + std::size(test));

错误信息:

couldn’t deduce template parameter ‘Vector’

手动指定模板参数倒是能跑:

auto d = distance<intptr_t, intptr_t>(std::data(test), std::data(test) + std::size(test));

三、问题与解决办法

核心疑问

能不能保留Vector和Scalar作为自由类型参数,同时让模板自动推导?还是必须要求点/向量类型自带可识别的类型别名(比如下面改的point Concept),但这样会限制兼容性——我们还想支持std::chrono这类仿射空间场景呢。

改写后的point Concept(依赖类型别名):

template<class T>
concept point = std::equality_comparable<T> && requires(T p1, T p2)
{
    typename T::vector_type;
    {p1 + v} -> std::same_as<T>;
    {p1 - v} -> std::same_as<T>;
    {p1 += v} -> std::same_as<T&>;
    {p1 -= v} -> std::same_as<T&>;
    {p1 - p2} -> std::same_as<T::vector_type>;
};

比如适配std::chrono:

static_assert(point<std::chrono::steady_clock::time_point, std::chrono::steady_clock::duration, long>);

不用类型别名的解决思路

不需要强制类型别名,通过从点的运算结果反向推导向量和标量类型就能实现自动推导:

方案1:调整Concept参数顺序,默认推导

把point Concept的点类型放在第一个,向量和标量类型设为默认参数,从点的运算结果推导:

// 辅助模板:从点类型获取向量类型
template<class P>
using vector_from_point = decltype(std::declval<P>() - std::declval<P>());

// 辅助模板:从向量类型推导标量类型
template<class V>
struct scalar_from_vector {
    template<class S>
    static auto test(int) -> std::enable_if_t<vector<V, S>, S>;
    template<class S>
    static auto test(...) -> void;
    using type = decltype(test<scalar>(0));
};
template<class V>
using scalar_from_vector_t = typename scalar_from_vector<V>::type;

// 调整后的point概念
template<class T, class VectorType = vector_from_point<T>, class ScalarType = scalar_from_vector_t<VectorType>>
concept point = std::equality_comparable<T>
    && vector<VectorType, ScalarType>
    && requires(T p1, T p2, VectorType v)
{
    {p1 + v} -> std::same_as<T>;
    {p1 - v} -> std::same_as<T>;
    {p1 += v} -> std::same_as<T&>;
    {p1 -= v} -> std::same_as<T&>;
    {p1 - p2} -> std::same_as<VectorType>;
};

这样定义distance函数时,只需要指定点类型参数,编译器会自动推导出向量和标量:

template<point P>
constexpr auto distance(P p1, P p2)
{
    return norm(p1 - p2);
}

方案2:直接在函数约束里推导

如果不想改原来的point Concept,也可以在distance函数的约束里直接推导向量和标量类型:

// 复用上面的scalar_from_vector_t辅助模板
template<class V>
struct scalar_from_vector {
    template<class S>
    static auto test(int) -> std::enable_if_t<vector<V, S>, S>;
    template<class S>
    static auto test(...) -> void;
    using type = decltype(test<scalar>(0));
};
template<class V>
using scalar_from_vector_t = typename scalar_from_vector<V>::type;

template<class P>
constexpr auto distance(P p1, P p2)
{
    using Vector = decltype(p1 - p2);
    using Scalar = scalar_from_vector_t<Vector>;
    static_assert(point<P, Vector, Scalar>);
    return norm(p1 - p2);
}

这两种方式都不用强制类型别名,既能保留Vector和Scalar作为自由参数,又能让模板自动推导,同时兼容指针、std::chrono::time_point这类没有内置类型别名的类型。

内容的提问来源于stack exchange,提问作者user877329

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 21:35:19