【发布时间】: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-type和Lower和Upper函数范围的限制。 -
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
这是我的工作程序,它使用各种函数从我的类中生成值数据集:
main.cpp
#include <cmath>
#include <exception>
#include <iostream>
#include "Function.h"
int main() {
try {
pipes::Functor<int, int> functor1;
functor1.FuncPtr = □
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";
因此,如果我希望能够为我的 intervals 或 Limits 或 Range 类支持浮点类型...如果目前在 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<double, range(-1.0,1.0), double>也不起作用,因为参数-1.0和1.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