模板化代码中的float或double

时间:2015-07-04 10:09:51

标签: c++ templates constexpr

以下示例可能看似荒谬,但它是更高性能代码的一部分,其中所呈现的技术是有意义的。我提到这个以防万一有人应该怀疑一个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。)

4 个答案:

答案 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>>;

需要变量指向,但不太迟钝。