为一个类编写一个 C++ 概念,该类具有一个采用任意大小的 std::array 的方法

问题描述 投票:0回答:1

背景

我正在编写数字积分器作为库的一部分。您可能知道,有很多不同类型的数值积分器(牛顿科特斯、高斯求积等),我希望它们可以互换。通常,人们可以通过拥有集成器的接口,然后拥有一些实现该接口的具体类型来实现这一点。然而,我的积分器需要能够集成任何算术类型的函数(这是我自己编写它的部分原因),即它们需要支持 double 但也支持 DoubleDouble 以及任何合理地表现为真实的东西数字表示。因此,我决定将积分器编写为模板类,并将需要积分器的任何其他函数作为模板函数,其中积分器将是模板参数。但是,我希望此模板参数受到某些集成器概念的约束。

最小可重现示例

#include <iostream>
#include <array>
#include <type_traits>
#include <functional>

template<typename F, typename Ret, typename A, typename... Rest>
A
helper(Ret(F::*)(A, Rest...));

template<typename F, typename Ret, typename A, typename... Rest>
A
helper(Ret(F::*)(A, Rest...) const);

template<typename F>
struct GetFirstArgumentOf {
    typedef std::remove_cvref_t<decltype(helper(&F::operator()))> type;
};

template<typename F>
using GetReturnTypeOf =
typename decltype(std::function{ std::declval<F>() })::result_type;

template<typename F, typename T>
concept IsFunctionFromR = requires(GetFirstArgumentOf<F>::type argument) {
    [](std::array<T, 1>) {}(argument); //checks argument type (TODO should work for copy or const reference)
};

template<typename F, typename T>
concept IsFunctionToR = requires(GetReturnTypeOf<F> return_) {
    [](T) {}((T) return_);
};

template<typename F, typename T>
concept IsFunctionFromRN = requires(GetFirstArgumentOf<F>::type argument) {
    []<std::size_t N>(std::array<T, N>) {}(argument);
};

template<typename F, typename T>
concept IsFunctionFromRToR = IsFunctionFromR<F, T> && IsFunctionToR<F, T>;

template<typename F, typename T>
concept IsFunctionFromRNToR = IsFunctionFromRN<F, T> && IsFunctionToR<F, T>;

template<typename T = double>
class Integrator {
public:
    template<typename F>
        requires IsFunctionFromRToR<F, T>
    T Integrate(const F& f, std::array<T, 1> a, std::array<T, 1> b) const {
        return (b[0] - a[0]) * (f(a) + f(b)) / 2.0;
    }

    template<typename F>
        requires IsFunctionFromRNToR<F, T> && (!IsFunctionFromRToR<F, T>)
    auto Integrate(const F& f, std::array<T, 1> a, std::array<T, 1> b) const {
        constexpr int dimension = std::tuple_size<typename GetFirstArgumentOf<F>::type>::value;
        return [=](std::array<T, dimension - 1> y) {
            auto f_at_y = [&](std::array<T, 1> x) {
                std::array<T, dimension> argument{0};
                argument[0] = x[0];
                for(int i = 1; i < dimension; i++) {
                    argument[i] = y[i - 1];
                }
            return f(argument);
            };
        return Integrate(f_at_y, a, b);
        };
    }
};

int main() {
    Integrator<double> i;
    auto f = [](std::array<double, 2> x) {
        return x[0] + x[1] * x[1];
    };
    std::array<double, 1> a = { 0 };
    std::array<double, 1> b = { 1 };
    std::cout << i.Integrate(i.Integrate(f, a, b), a, b) << std::endl;

    return 0;
}

Integrator 类有两种 Integrate 方法:一种用于 1 个变量的函数 (std::array),返回 T 值标量;另一种用于 N 个变量的函数,返回 N-1 个变量的函数,其中第一个变量已积分。

顺便说一句,使用在编译时已知大小的容器是不可协商的(问题中的 std::array ,在我的实际代码中,我使用大小为 N 的模板化类)

问题

那么,我想知道是否可以为这样的积分器编写一个概念,以便我可以写:

template<IntegratorConcept I>
void DoSomethingWithIntegrator(I integrator);

我知道我可以只写“typename I”而不是“IntegratorConcept I”,并且只有当类型 I 提供必要的接口时它才会起作用,但我想知道是否可以通过概念使该接口更加明确.

我研究过的内容

我知道我无法创建一个作为模板的接口(又名纯虚拟类),这可以非常优雅地解决我的问题。

我能找到的最接近的其他想法是这篇博客文章,它开始尝试编写一个在requires子句中使用另一个概念的概念(这不起作用),然后引入原型的想法。然而,为了完成这项工作,我需要创建无限数量的原型,每个原型对应 n 维函数的每个维度。

提前感谢您的回答。

c++ templates interface c++-concepts
1个回答
0
投票

可以为这样的积分器写一个概念,这样我就可以写:...

是的。您可以从一个简单的类型特征开始:

template<class T>
struct is_integrator : std::false_type {};

template<class T>
struct is_integrator<Integrator<T>> : std::true_type {};

用于

concept

template<class T>
concept IntegratorConcept = is_integrator<T>::value;

您也可以直接在函数中使用

concept

void DoSomethingWithIntegrator(IntegratorConcept auto integrator) {
    //...
}
© www.soinside.com 2019 - 2024. All rights reserved.