模板代码中的 float 或 double
float or double in templated code
下面的示例可能看起来很荒谬,但它是一个更大的高性能代码的一部分,在这个代码中所介绍的技术是有意义的。我提到这一点是为了防止有人怀疑 XY 问题——很可能不是。
我有一个带有 templated/compile-time 操作数的函数:
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/float 的解决方案感兴趣,而不是使用两个整数来创建有理数或定点想法(例如 y = 0.01 * M * x)的黑客攻击。)
在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;}
如果您不想为每个使用的 double/float 常量创建类型信封,那么您可以创建整数和双精度常量之间的映射。这样的映射可以实现,例如如下:
#include <string>
#include <sstream>
template<int index> double getValue()
{
std::stringstream ss("Not implemented for index ");
ss << index;
throw std::exception(ss.str());
}
template<> double getValue<0>() { return 3.6; }
template<> double getValue<1>() { return 7.77; }
template<int index> double multiply(double x)
{
return getValue<index>() * x;
}
实现映射的替代选项是通过一个函数,该函数对输入整数参数和 returns 一个 float
/double
或索引到一个数组常量,但这两种选择都需要 constexpr
才能在编译时发生,而一些编译器仍然不支持 constexpr
:constexpr not compiling in VC2013
constexpr double make_double( int64_t v, int64_t man );
编写一个函数,从基数和尾数生成双精度数。这可以代表每一个非特殊的双倍。
然后写:
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>>;
这需要一个变量来指向,但不那么迟钝。
您可以使用用户定义的文字方便地在模板参数中传递浮点值。
只需编写一个文字来创建您的信封 class。然后你可以写类似
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;
}
下面的示例可能看起来很荒谬,但它是一个更大的高性能代码的一部分,在这个代码中所介绍的技术是有意义的。我提到这一点是为了防止有人怀疑 XY 问题——很可能不是。
我有一个带有 templated/compile-time 操作数的函数:
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/float 的解决方案感兴趣,而不是使用两个整数来创建有理数或定点想法(例如 y = 0.01 * M * x)的黑客攻击。)
在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;}
如果您不想为每个使用的 double/float 常量创建类型信封,那么您可以创建整数和双精度常量之间的映射。这样的映射可以实现,例如如下:
#include <string>
#include <sstream>
template<int index> double getValue()
{
std::stringstream ss("Not implemented for index ");
ss << index;
throw std::exception(ss.str());
}
template<> double getValue<0>() { return 3.6; }
template<> double getValue<1>() { return 7.77; }
template<int index> double multiply(double x)
{
return getValue<index>() * x;
}
实现映射的替代选项是通过一个函数,该函数对输入整数参数和 returns 一个 float
/double
或索引到一个数组常量,但这两种选择都需要 constexpr
才能在编译时发生,而一些编译器仍然不支持 constexpr
:constexpr not compiling in VC2013
constexpr double make_double( int64_t v, int64_t man );
编写一个函数,从基数和尾数生成双精度数。这可以代表每一个非特殊的双倍。
然后写:
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>>;
这需要一个变量来指向,但不那么迟钝。
您可以使用用户定义的文字方便地在模板参数中传递浮点值。
只需编写一个文字来创建您的信封 class。然后你可以写类似
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;
}