以下示例可能看似荒谬,但它是更高性能代码的一部分,其中所呈现的技术是有意义的。我提到这个以防万一有人应该怀疑一个XY问题 - 这很可能不是。
我有一个带模板化/编译时操作数的函数:
template <int M>
int mul(int x){
return M * x;
}
现在我想为double
做同样的事情,当然 - 不允许:
template <double M> // you can't do that!
int mul(double x){
return M * x;
}
所以在编译时仍然放入double
,我只看到以下解决方案:
// create my constants
struct SevenPointFive{
static constexpr double VAL = 7.5;
}
struct ThreePointOne{
static constexpr double VAL = 3.1;
}
// modified function
template <class M>
int mul(double x){
return M::VAL * x;
}
// call it
double a = mul<SevenPointFive>(3.2);
double b = mul<ThreePointOne>(a);
是否有一个更好的解决方案,以某种方式在模板参数中传递一个double常量,而不为每个值创建一个struct?
(我对一个实际上使用double / float的解决方案感兴趣,而不是使用两个整数来创建一个有理数或一个固定点的想法,例如y = 0.01 * M * x。)
答案 0 :(得分:2)
在C ++ 11中,根本没有必要使用模板。只需以与您不同的方式使用constexpr
(广义常量表达式)。
#include <iostream>
constexpr double mul(double x, double y)
{
return x*y;
}
int main()
{
std::cout << mul(2.3, 3.4) << '\n';
double x;
std::cin >> x; // to demonstrate constexpr works with variables
std::cout << mul(2.3, x) << '\n';
}
虽然我说模板不是必需的(在给出的示例中它们不是),如果需要,可以模板化
template <class T> constexpr T mul(T x, T y) {return x*y;}
或(如果你想将这个函数用于const引用更好的类型)
template <class T> constexpr T mul(const T &x, const T &y) {return x*y;}
答案 1 :(得分:2)
您可以使用用户定义的文字在模板参数中方便地传递浮点值。
只需编写一个可以创建信封类的文字。然后你可以写点像
mul<decltype(3.7_c)>(7)
或者甚至更好,让你的函数按值获取参数,这样你就可以编写
mul(3.7_c, 7)
编译器会同样有效。
下面是执行此操作的代码示例:
#include <iostream>
template <int Value, char...>
struct ParseNumeratorImpl {
static constexpr int value = Value;
};
template <int Value, char First, char... Rest>
struct ParseNumeratorImpl<Value, First, Rest...> {
static constexpr int value =
(First == '.')
? ParseNumeratorImpl<Value, Rest...>::value
: ParseNumeratorImpl<10 * Value + (First - '0'), Rest...>::value;
};
template <char... Chars>
struct ParseNumerator {
static constexpr int value = ParseNumeratorImpl<0, Chars...>::value;
};
template <int Value, bool, char...>
struct ParseDenominatorImpl {
static constexpr int value = Value;
};
template <int Value, bool RightOfDecimalPoint, char First, char... Rest>
struct ParseDenominatorImpl<Value, RightOfDecimalPoint, First, Rest...> {
static constexpr int value =
(First == '.' && sizeof...(Rest) > 0)
? ParseDenominatorImpl<1, true, Rest...>::value
: RightOfDecimalPoint
? ParseDenominatorImpl<Value * 10, true, Rest...>::value
: ParseDenominatorImpl<1, false, Rest...>::value;
};
template <char... Chars>
using ParseDenominator = ParseDenominatorImpl<1, false, Chars...>;
template <int Num, int Denom>
struct FloatingPointNumber {
static constexpr float float_value =
static_cast<float>(Num) / static_cast<float>(Denom);
static constexpr double double_value =
static_cast<double>(Num) / static_cast<double>(Denom);
constexpr operator double() { return double_value; }
};
template <int Num, int Denom>
FloatingPointNumber<-Num, Denom> operator-(FloatingPointNumber<Num, Denom>) {
return {};
}
template <char... Chars>
constexpr auto operator"" _c() {
return FloatingPointNumber<ParseNumerator<Chars...>::value,
ParseDenominator<Chars...>::value>{};
}
template <class Val>
int mul(double x) {
return Val::double_value * x;
}
template <class Val>
int mul(Val v, double x) {
return v * x;
}
int main() {
std::cout << mul<decltype(3.79_c)>(77) << "\n";
std::cout << mul(3.79_c, 77) << "\n";
return 0;
}
答案 2 :(得分:1)
如果您不想为每个使用的double / float常量创建类型包络,则可以在整数和双常量之间创建映射。例如,可以实现这种映射。如下:
float
实现映射的替代选项是通过一个函数,该函数在输入整数参数上执行switch-case并返回double
/ constexpr
或索引到常量数组,但这两个选项都是需要constexpr
才能在编译时发生,而有些编译器仍然不支持{{1}}:constexpr not compiling in VC2013
答案 3 :(得分:1)
constexpr double make_double( int64_t v, int64_t man );
编写一个从基数和尾数中生成double的函数。这可以代表每个非特殊的双倍。
然后写:
template<int64_t v, int64_t man>
struct double_constant;
使用上述make_double
和各种constexpr
访问方法。
您甚至可以编写我怀疑的基本和指数提取constexpr
函数。添加宏以删除DRY,或使用变量。
另一种方法是:
const double pi=3.14;//...
template<double const* v>
struct dval{
operator double()const{return *v;}
};
template<class X>
double mul(double d){
return d*X{};
}
double(*f)(double)=mul<dval<&pi>>;
需要变量指向,但不太迟钝。