模板化代码中的float或double

float or double in templated code

本文关键字:float double 代码      更新时间:2023-10-16

下面的例子可能看起来毫无意义,但它是一个更大的高性能代码的一部分,其中所介绍的技术是有意义的。我提到这一点只是为了防止有人怀疑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/foat的解决方案,而不是使用两个int来创建有理数或不动点的想法,如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;}

您可以使用用户定义的文字方便地传递模板参数中的浮点值。

只需编写一个创建信封类的文字。然后你可以写一些类似的东西

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;                                                                  
}  
constexpr double make_double( int64_t v, int64_t man );

编写一个函数,从基数和尾数生成一个二重。这可以代表每一个非特殊的二重。

然后写:

template<int64_t v, int64_t man>
struct double_constant;

使用上述CCD_ 4和各种CCD_。

我怀疑您甚至可以编写提取基数和指数的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>>;

这需要一个变量来指向,但不那么迟钝。

如果您不想为使用的每个double/foat常量创建类型包络,那么您可以创建integer和double常量之间的映射。这种映射可以实现,例如如下:

#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;
}

实现映射的其他选项是通过一个函数来实现的,该函数在输入整数参数上切换大小写并返回float/double,或索引到常量数组,但这两种选项都需要constexpr才能在编译时发生,而一些编译器仍然不支持constexpr:constexpr不在VC2013 中编译