【问题标题】:Generating floating point limits at compile time via template arguments and constexpr semantics:在编译时通过模板参数和 constexpr 语义生成浮点限制:
【发布时间】:2020-07-28 04:31:32
【问题描述】:

我正在研究一组课程。我的Function 类将采用Functor 类,该类将function pointer 存储到某个已定义的函数,该函数具有将从函数指针调用函数调用的运算符。它使用一个Limit 类,该类当前采用<int,int> 作为其上限和下限。它只有static constexpr 函数来返回边界并计算这些边界之间的元素数量。如果下限 = 1 和上限 = 5,它将为要为该函数评估的元素数生成 5...

这是我对这些类所做的:

  • 首先我声明一个函数,例如 f(x) = x、f(x) = x^2 或 f(x) = cos(x) 等。
  • 然后我根据上述函数参数类型为返回及其参数参数类型实例化一个Functor 对象...
  • 接下来,我将函数分配给我的Functor 类的成员变量。
  • 然后,我实例化一个 Function 对象,为它提供 data-typeLowerUpper 函数范围的限制。
  • Function 类在构造时会自动从 [lower,upper] 生成该函数的数据点,并将生成的值存储在其内部数组中。
  • Function 类还包含一个 operator,它允许用户从任何给定的输入中获取任何值。

伪示例:

f(x) = x^2;

Functor<T,T> functor;
functor.member = &f(x);
Function<T,Lower,Upper,T> function(functor); 

// If T=int, Lower = -4, and Upper = 4 then the internal data set will be 
// (-4,16) (-3,9), (-2,4), (-1,1), (0,0), (1,1), (2,4), (3,9), (4,16)
// The user can also use it's operator to call function(9) and it will return 81

这是我的工作程序,它使用各种函数从我的类中生成值数据集:

ma​​in.cpp

#include <cmath>
#include <exception>
#include <iostream>

#include "Function.h"

int main() {
    try {
        pipes::Functor<int, int> functor1;
        functor1.FuncPtr = &square;
        pipes::Function<int, -10, 10, int> func1( functor1 );
        auto data1{ func1.data() };
        for (auto& p : data1)
            std::cout << '(' << p.first << ',' << p.second << ")\n";
        std::cout << '\n';
        std::cout << "f(25) = " << func1(25) << "\n\n";

        pipes::Functor<int, int> functor2;
        functor2.FuncPtr = &linear;
        pipes::Function<int, -10, 10, int> func2(functor2);
        auto data2{ func2.data() };
        for (auto& p : data2)
            std::cout << '(' << p.first << ',' << p.second << ")\n";
        std::cout << '\n';
        std::cout << "f(25) = " << func2(25) << "\n\n";

        pipes::Functor<double, double> functor3;
        functor3.FuncPtr = &cosine;
        pipes::Function<double, -7, 7, double> func3(functor3);
        auto data3{ func3.data() };
        for (auto& p : data3)
            std::cout << '(' << p.first << ',' << p.second << ")\n";
        std::cout << '\n';
        std::cout << "f(25) = " << func3(25) << "\n\n";

    }
    catch (const std::exception& e) {
        std::cerr << e.what() << "\n\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

函数.h

#pragma once

#include <array>

namespace pipes {

    template<typename Ret, typename... Args>
    struct Functor {
        Ret(*FuncPtr)(Args...);
        Ret operator()(Args... args) { return FuncPtr(args...); }
    };

    template<int Lower, int Upper>
    class Limits {
    public:
        static constexpr unsigned lower_bound() { return Lower; }
        static constexpr unsigned upper_bound() { return Upper; }
        static constexpr unsigned element_count() { return (Upper - Lower + 1); }
    };

    template<typename T, int Lower, int Upper, typename... Args>
    class Function {        
        std::array<std::pair<T, T>, Limits<Lower,Upper>::element_count()> data_points_;
        Functor<T,Args...> functor_;
    public:
        Function(Functor<T,Args...> func) {
            functor_ = func;
            for (unsigned i = 0; i < Limits<Lower,Upper>::element_count(); i++) {
                data_points_[i].first = ((T)i + (T)Lower);
                data_points_[i].second = functor_(data_points_[i].first);
            }
        }

        T operator()(Args... args) const { 
            return functor_.FuncPtr(args...);
        }

        constexpr auto lower() const { return Lower; }
        constexpr auto upper() const { return Upper; }
        constexpr auto count() const { return Limits<Lower,Upper>::element_count(); }
        constexpr auto data() const { return data_points_; }
    };

} // namespace pipes

当我运行程序时,它会生成这个看起来是正确的输出:

输出

(-10,100)
(-9,81)
(-8,64)
(-7,49)
(-6,36)
(-5,25)
(-4,16)
(-3,9)
(-2,4)
(-1,1)
(0,0)
(1,1)
(2,4)
(3,9)
(4,16)
(5,25)
(6,36)
(7,49)
(8,64)
(9,81)
(10,100)

f(25) = 625

(-10,-10)
(-9,-9)
(-8,-8)
(-7,-7)
(-6,-6)
(-5,-5)
(-4,-4)
(-3,-3)
(-2,-2)
(-1,-1)
(0,0)
(1,1)
(2,2)
(3,3)
(4,4)
(5,5)
(6,6)
(7,7)
(8,8)
(9,9)
(10,10)

f(25) = 25

(-7,0.753902)
(-6,0.96017)
(-5,0.283662)
(-4,-0.653644)
(-3,-0.989992)
(-2,-0.416147)
(-1,0.540302)
(0,1)
(1,0.540302)
(2,-0.416147)
(3,-0.989992)
(4,-0.653644)
(5,0.283662)
(6,0.96017)
(7,0.753902)

f(25) = 0.991203

现在我的问题是这成为棘手的部分......

就我目前的代码而言,只要我的边界 [-a,b]integral 类型,一切都很好...

假设在我的上一个示例中,例如cos,如果我想从[-2pi,2pi] 获得我的界限,其中下限和上限是浮点类型...

问题:

目前在 C++ 中这是非标准的,在大多数情况下不会编译:

template<float val> // or template<double>
struct foo() {
    constexpr float operator()() {
        return val;
    }
};

以上内容阻止我做这样的事情:

constexpr double PI{ 6.28318531 };

pipes::Functor<double, double> functor3;
functor3.FuncPtr = &cosine;
pipes::Function<double, -PI, PI, double> func3(functor3);
auto data3{ func3.data() };
for (auto& p : data3)
    std::cout << '(' << p.first << ',' << p.second << ")\n";
std::cout << '\n';
std::cout << "f(25) = " << func3(25) << "\n\n";   

因此,如果我希望能够为我的 intervalsLimitsRange 类支持浮点类型...如果目前在 c++ 中可以实现这样的事情,会有什么样的替代方案?还是我只需要简单地重构我的类模板的设计方式?

如果在编译期间通过模板和 constexpr 语义可以以某种方式实现上述目标,那么就会出现另一个必须考虑的问题,那就是与浮点类型一起使用的步进间隔知道数据集中将有多少个数据点...(基本上根据用户定义的一些步进值计算dx,例如:(0.1、0.001 等...)和数据点将通过 [lower, upper] 之间的这些除法的数量来计算...但是,如果在编译时已知步进值,那么计算除法应该足够简单...这不是主要问题。更大的问题是能够在编译时表达浮点常量以进行模板评估...

目前,按照我的代码设计方式,我的功能受到了限制...我不确定如何提供类似的接口来支持可以计算和生成的floating-point 范围在编译时!欢迎任何帮助或建议!

【问题讨论】:

  • 目前,只有当你为每个浮点范围指定一个自己的类型,如struct pi_range { constexpr double lower = -IP , constexpr double upper = IP};(或分别为每个下限和上限)并使用pi_range作为参数:@987654354 @
  • @t.niese 我有点理解你的意思,但是,我不想为不同的范围显式创建多个结构,我希望能够“概括”它!
  • 使用consteval 可能是可能的,但我还没有检查过(特别是因为它是 c++20),但只要该值以某种方式需要识别模板这是不可能的。所以即使pipes::Function&lt;double, range(-1.0,1.0), double&gt; 也不起作用,因为参数-1.01.0 是模板标识的一部分。您不需要为范围设置结构,您也可以只为下部和上部设置一个单独的结构。
  • @t.niese 我现在正在尝试一些替代方案,我已经接近了,但是,它迫使我使用std::vector 而不是std::array。我试图将array 用于小样本大小以对堆栈和缓存友好,但必须提前知道大小是使它变得困难的原因。使用vector 代替,我不需要知道它的大小,并且可以在Function 的构造函数中计算它,我确实有一个工作示例,但是,f(x)x 部分因为floating point function 没有给我正确的值。这可能是由于强制转换和截断。
  • @t.niese 您可以查看我的修改版本的答案,该版本将允许floating-point 限制,除非它不能在编译时完成,但可以在运行期间实现 -时间...也许有一天,C++ 将支持浮点编译时间常量作为模板参数...

标签: c++ templates floating-point c++17 compile-time-constant


【解决方案1】:

我认为最接近像你这样的构造的是:

#include <iostream>
#include <array>

constexpr const double PI_2{ 6.28318531 };

template<double const &lower, double const &upper>
void foo() {
    static_assert(lower<upper, "invalid lower and upper value");
    
    constexpr size_t size = (upper-lower);
    std::array<int, size> test;
    
    std::cout << lower << " " << upper << " " << test.size() << std::endl;
}

template<double const &V>
struct neg {
  static constexpr double value = -V;
};

int main()
{
    foo<neg<PI_2>::value, PI_2>();
    
    return 0;
}

如果你总是可以将类型指定为第一个模板参数,你可以有这样的东西:

template<typename T, T const &lower, T const &upper>
void foo() {
    std::cout << lower << " " << upper << std::endl;
}

我还没有完全考虑清楚,如何将浮点部分和另一个放在一起,但我认为应该是可能的。

【讨论】:

  • 不错的提议。我理解为什么 C++ 目前不支持浮点常量作为模板参数。这是因为浮点实现不是从一台机器或操作系统到下一台的标准......如果每台机器都实现 IEE-754,那么是的,C++ 有可能支持它,但是,事实并非如此,因为也就是说,一台机器上的编译时浮点常量在另一台机器上的解释可能不同......理论上可以做到,但在实践中,这些是我们需要处理的限制。
  • @FrancisCugler 是的,这就是我在对您的问题的评论中识别模板的意思。只要至少在编译时评估中没有指定浮点数的表示,它就不会成为规范的一部分。
【解决方案2】:

在现代 C++ 和当前模板的设计方式中,我不得不稍微重构我的代码。这迫使我不得不使用std::vector 而不是std::array,因为我们不能使用floating-point types 作为常量模板参数......所以我最终不得不改变我的两个类......我不得不改变我的Limits 班级和我的Function 班级。

我的Limits 类现在接受Type 而不是constant-integral-type,它存储了3 个成员变量。它还有一个默认构造函数和一个用户构造函数。这些函数现在只是 constexpr 而不是静态的。

我的Function 类现在存储一个Limits 类对象,data_points_ 不再是std::array,因为它现在是std::vector。它的构造函数现在也接受一个Limits 对象。

我还考虑了 floating-point 范围的 step 大小。

这是我修改后的代码及其给定输出的样子:

ma​​in.cpp

#include <cmath>
#include <iostream>
#include <exception>

#include "Function.h"

constexpr int square(int x) {
    return x * x;
}

constexpr int linear(int x) {
    return x;
}

double cosine(double x) {
    return cos(x);
}

//template<float val>
struct foo {
    float operator()(float val) { return val; }
};

int main() {
    try {
        pipes::Functor<int, int> functor1;
        pipes::Limits<int> limit1(-10, 10, 1);
        functor1.FuncPtr = &square;
        pipes::Function<int, int, int> func1( limit1, functor1 );
        auto data1{ func1.data() };
        for (auto& p : data1)
            std::cout << '(' << p.first << ',' << p.second << ")\n";
        std::cout << '\n';
        std::cout << "f(25) = " << func1(25) << "\n\n";

        pipes::Functor<int,int> functor2;
        pipes::Limits<int> limit2(-10, 10, 1);
        functor2.FuncPtr = &linear;
        pipes::Function<int, int, int> func2(limit2, functor2);
        auto data2{ func2.data() };
        for (auto& p : data2)
            std::cout << '(' << p.first << ',' << p.second << ")\n";
        std::cout << '\n';
        std::cout << "f(25) = " << func2(25) << "\n\n";

        constexpr double PI{ 6.28318531 };

        pipes::Functor<double, double> functor3;
        pipes::Limits<double> limits3( (-PI), PI, 0.1);
        functor3.FuncPtr = &cosine;
        pipes::Function<double, double, double> func3(limits3, functor3);
        auto data3{ func3.data() };
        for (auto& p : data3)
            std::cout << '(' << p.first << ',' << p.second << ")\n";
        std::cout << '\n';
        std::cout << "f(25) = " << func3(25) << "\n\n";

    }
    catch (const std::exception& e) {
        std::cerr << e.what() << "\n\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

函数.h

#pragma once

#include <vector>

namespace pipes {

    template<typename Ret, typename... Args>
    struct Functor {
        Ret(*FuncPtr)(Args...);
        Ret operator()(Args... args) { return FuncPtr(args...); }
    };

    template<typename Ty>
    class Limits {
    private:
        Ty Lower;
        Ty Upper;
        Ty Step;

    public:
        Limits() {}
        Limits(Ty lower, Ty upper, Ty step) : Lower{ lower }, Upper{ upper }, Step{ step } {}

        constexpr Ty lower_bound() { return Lower; }
        constexpr Ty upper_bound() { return Upper; }
        constexpr Ty step_size() { return Step; }
        constexpr unsigned element_count() { return (unsigned)((Upper - Lower + 1)/Step); }
    };

    template<typename LimT, typename FuncT, typename... Args>
    class Function {
        Limits<LimT> limits_;
        Functor<FuncT, Args...> functor_;
        std::vector<std::pair<FuncT, FuncT>> data_points_;
    public:
        Function(Limits<LimT> limits, Functor<FuncT,Args...> func) { 
            limits_ = limits;
            functor_ = func;
            data_points_.resize( limits_.element_count() );
            for (unsigned i = 0; i < limits_.element_count(); i++) {
                auto x = limits_.lower_bound() + (i * limits_.step_size());
                data_points_[i].first = (x);
                data_points_[i].second = functor_(x);
            }
        }

        FuncT operator()(Args... args) const { 
            return functor_.FuncPtr(args...);
        }

        constexpr auto lower() const { return limits_.lower_bound(); }
        constexpr auto upper() const { return limits_.upper_bound(); }
        constexpr auto count() const { return limits_.element_count(); }
        constexpr auto step() const { return limits_.step_size(); }
        constexpr auto data() const { return data_points_; }
    };

} // namespace pipes

输出

(-10,100)
(-9,81)
(-8,64)
(-7,49)
(-6,36)
(-5,25)
(-4,16)
(-3,9)
(-2,4)
(-1,1)
(0,0)
(1,1)
(2,4)
(3,9)
(4,16)
(5,25)
(6,36)
(7,49)
(8,64)
(9,81)
(10,100)

f(25) = 625

(-10,-10)
(-9,-9)
(-8,-8)
(-7,-7)
(-6,-6)
(-5,-5)
(-4,-4)
(-3,-3)
(-2,-2)
(-1,-1)
(0,0)
(1,1)
(2,2)
(3,3)
(4,4)
(5,5)
(6,6)
(7,7)
(8,8)
(9,9)
(10,10)

f(25) = 25

(-6.28319,1)
(-6.18319,0.995004)
(-6.08319,0.980067)
(-5.98319,0.955336)
(-5.88319,0.921061)
(-5.78319,0.877583)
(-5.68319,0.825336)
(-5.58319,0.764842)
(-5.48319,0.696707)
(-5.38319,0.62161)
(-5.28319,0.540302)
(-5.18319,0.453596)
(-5.08319,0.362358)
(-4.98319,0.267499)
(-4.88319,0.169967)
(-4.78319,0.0707372)
(-4.68319,-0.0291995)
(-4.58319,-0.128844)
(-4.48319,-0.227202)
(-4.38319,-0.32329)
(-4.28319,-0.416147)
(-4.18319,-0.504846)
(-4.08319,-0.588501)
(-3.98319,-0.666276)
(-3.88319,-0.737394)
(-3.78319,-0.801144)
(-3.68319,-0.856889)
(-3.58319,-0.904072)
(-3.48319,-0.942222)
(-3.38319,-0.970958)
(-3.28319,-0.989992)
(-3.18319,-0.999135)
(-3.08319,-0.998295)
(-2.98319,-0.98748)
(-2.88319,-0.966798)
(-2.78319,-0.936457)
(-2.68319,-0.896758)
(-2.58319,-0.8481)
(-2.48319,-0.790968)
(-2.38319,-0.725932)
(-2.28319,-0.653644)
(-2.18319,-0.574824)
(-2.08319,-0.490261)
(-1.98319,-0.400799)
(-1.88319,-0.307333)
(-1.78319,-0.210796)
(-1.68319,-0.112153)
(-1.58319,-0.0123887)
(-1.48319,0.087499)
(-1.38319,0.186512)
(-1.28319,0.283662)
(-1.18319,0.377978)
(-1.08319,0.468517)
(-0.983185,0.554374)
(-0.883185,0.634693)
(-0.783185,0.70867)
(-0.683185,0.775566)
(-0.583185,0.834713)
(-0.483185,0.88552)
(-0.383185,0.927478)
(-0.283185,0.96017)
(-0.183185,0.983268)
(-0.0831853,0.996542)
(0.0168147,0.999859)
(0.116815,0.993185)
(0.216815,0.976588)
(0.316815,0.950233)
(0.416815,0.914383)
(0.516815,0.869397)
(0.616815,0.815725)
(0.716815,0.753902)
(0.816815,0.684547)
(0.916815,0.608351)
(1.01681,0.526078)
(1.11681,0.438547)
(1.21681,0.346635)
(1.31681,0.25126)
(1.41681,0.153374)
(1.51681,0.0539554)
(1.61681,-0.0460021)
(1.71681,-0.1455)
(1.81681,-0.243544)
(1.91681,-0.339155)
(2.01681,-0.431377)
(2.11681,-0.519289)
(2.21681,-0.602012)
(2.31681,-0.67872)
(2.41681,-0.748647)
(2.51681,-0.811093)
(2.61681,-0.865435)
(2.71681,-0.91113)
(2.81681,-0.947722)
(2.91681,-0.974844)
(3.01681,-0.992225)
(3.11681,-0.999693)
(3.21681,-0.997172)
(3.31681,-0.984688)
(3.41681,-0.962365)
(3.51681,-0.930426)
(3.61681,-0.889191)
(3.71681,-0.839072)
(3.81681,-0.780568)
(3.91681,-0.714266)
(4.01681,-0.640826)
(4.11681,-0.560984)
(4.21681,-0.475537)
(4.31681,-0.385338)
(4.41681,-0.291289)
(4.51681,-0.19433)
(4.61681,-0.0954289)
(4.71681,0.0044257)
(4.81681,0.104236)
(4.91681,0.203005)
(5.01681,0.299745)
(5.11681,0.393491)
(5.21681,0.483305)
(5.31681,0.56829)
(5.41681,0.647596)
(5.51681,0.720432)
(5.61681,0.78607)
(5.71681,0.843854)
(5.81681,0.893206)
(5.91681,0.933634)
(6.01681,0.964733)
(6.11681,0.986192)
(6.21681,0.997798)
(6.31681,0.999435)
(6.41681,0.991085)
(6.51681,0.972833)
(6.61681,0.94486)
(6.71681,0.907447)
(6.81681,0.860967)
(6.91681,0.805884)
(7.01681,0.742749)
(7.11681,0.672193)

f(25) = 0.991203

这给了我想要的行为,但是,我试图使用 array 做同样的事情...我猜直到 C++ 支持 floating-point-constants 作为模板参数我要去必须使用堆分配来解决 std::vector,而不是 std::array 和堆栈缓存友好容器...

【讨论】:

    猜你喜欢
    • 2013-03-11
    • 2022-01-19
    • 2013-11-18
    • 2015-07-27
    • 2016-04-28
    • 1970-01-01
    • 2012-08-27
    • 1970-01-01
    • 2013-10-01
    相关资源
    最近更新 更多