模板代码中的 float 或 double

4
以下示例可能看起来毫无意义,但它是一个更大的高性能代码的一部分,所以所呈现的技术是有意义的。我提到这一点只是为了防止有人怀疑这是一个 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/float的解决方案,而不是使用两个int创建有理数或固定点思想的hack,例如y = 0.01 * M * x。)


3
这里不需要模板,只需使用重载函数。 - user2249683
4
@Michael 可以考虑几种不同的方法;不确定是否好用,您可以针对有理数基于 <size_t nom, size_t denom> 进行模板化。 - Ami Tavory
@DieterLücking:重载可以以何种方式替代模板并避免代码重复? - user1196549
@AmiTavory和Yves Daous:感谢你们的回答。在许多/大多数情况下,这些可能是合理的,但我对实际浮点解决方案很感兴趣。 - Michael
@DieterLücking:重载可以在这里起到什么作用? - Michael
显示剩余3条评论
4个回答

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

展示constexpr如何与变量一起使用 - 我不知道这个! - Ajay
也许我没有表达清楚,但问题的重点在于如何将双精度常量放入模板参数中。 - Michael
1
是的,我明白了。但是你的解释暗示了你想要在编译时进行评估的原因。这正是constexpr方法在参数值在编译时已知的情况下所实现的。我的回应是关于你(所说)想要实现的目标。mul<2.3>(3.4)(如果允许的话)和带有constexprmul(2.3, 3.4)之间唯一的区别在于使用时的语法,而不是功能上的区别。 - Peter

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

顺便提一下,为了使其成为一个完整的解决方案,您还需要处理科学计数法的解析(例如7.5e5_c); 但是为了保持示例简单,我会忽略它。 - Ryan Burn

1
如果您不想为每个使用的double/float常量创建类型信封,则可以创建整数和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;
}

实现映射的另一种选择是通过一个函数,该函数对输入整数参数进行switch-case操作并返回float/double,或者索引到一个常量数组,但这两种替代方案都需要constexpr才能在编译时发生,而一些编译器仍不支持constexpr: VC2013中constexpr无法编译


1
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>>;

需要一个变量指向它,但不那么晦涩。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接