4

The following example might seem nonsensical, but it's part of a larger high-performance code where the presented technique makes sense. I mention this just in case someone should suspect an XY question - it's most probably not.

I have a function with templated/compile-time operand:

template <int M>
int mul(int x){
  return M * x;
}

Now I want to do the same for double, what is - of course - not allowed:

template <double M> // you can't do that!
int mul(double x){
  return M * x;
}

So to still put in the double at compile time, I only see the following solution:

// 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);

Is there a better solution for the problem to somehow pass a double constant in a template parameter, without creating a struct for each value?

(I'm interested in a solution which actually uses double/float, not a hack with using two ints to create a rational number or fixed point ideas such as y = 0.01 * M * x.)

Michael
  • 7,407
  • 8
  • 41
  • 84
  • 3
    You do not need templates here, just overloaded functions. –  Jul 04 '15 at 10:11
  • 4
    @Michael various hacks come to mind; not sure if good or not - you could template base on `` for rational numbers, e.g.. – Ami Tavory Jul 04 '15 at 10:14
  • Quick & dirty workaround: template int mul(int x){ return (0.1 * M) * x; }, instantiate with 75 and 31. –  Jul 04 '15 at 10:15
  • @DieterLücking: in what way can overloading replace templates and avoid code duplication ?? –  Jul 04 '15 at 10:24
  • @AmiTavory and Yves Daous: Thank's for your answers. These might be reasonable in many/most situations, but I'd be interested in a real floating point solution here. – Michael Jul 04 '15 at 10:32
  • @DieterLücking: How can overloading help here? – Michael Jul 04 '15 at 10:32
  • MSVC++ 2013 still does not support constexpr: http://stackoverflow.com/questions/20264644/constexpr-not-compiling-in-vc2013 . So you have to either drop constexpr or MSVC++2013 support. – Serge Rogatch Jul 04 '15 at 11:01
  • Are they in practice arbitrary doubles? Or is there some pattern? Are you using ieee floating points? – Yakk - Adam Nevraumont Jul 04 '15 at 11:18

4 Answers4

2

In C++11, it is not necessary to use templates at all. Simply use constexpr (generalised constant expressions) in a different way than you are.

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

Although I say templates aren't necessary (which they aren't in the example given) these can be templated if needed

 template <class T> constexpr T mul(T x, T y) {return x*y;}

or (if you want to use the function for types that are better passed by const reference)

 template <class T> constexpr T mul(const T &x, const T &y) {return x*y;}
Peter
  • 35,646
  • 4
  • 32
  • 74
  • "to demonstrate constexpr works with variables" - I didn't know this! – Ajay Jul 04 '15 at 11:00
  • May be I was not clear enough about this, but the point of the question is on how to put a double constant into a template parameter. – Michael Jul 04 '15 at 11:21
  • 1
    Yeah, I got that. But your explanation suggested the reason is that you want compile-time evaluation. Which is what the `constexpr` approach achieves when the arguments values are known at compile time. My response was about what (you say) you are trying to achieve. The only difference between `mul<2.3>(3.4)` (if that was allowed) and `mul(2.3, 3.4)` with `constexpr` is syntactic at the point of use - not functional. – Peter Jul 04 '15 at 11:33
2

You can conveniently pass floating point values in template parameters using user-defined literals.

Just write a literal that creates your envelope class. Then you can write something like

mul<decltype(3.7_c)>(7)

Or even better, have your function take the argument by value so you can write

mul(3.7_c, 7)

the compiler will make that just as efficient.

Below's an example of code that does this:

#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;                                                                  
}  
Ryan Burn
  • 2,126
  • 1
  • 14
  • 35
  • BTW to make it a complete solution, you'd also need to handle parsing scientific notation (e.g. 7.5e5_c); but I'll leave that out to keep the example simple. – Ryan Burn Jul 04 '15 at 21:26
1

If you don't want to create type envelopes for each double/float constant used, then you can create a mapping between integer and double constants. Such mapping can be implemented e.g. as follows:

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

Alternative options to implement the mapping are via a function which does switch-case on the input integer parameter and returns a float/double, or indexing to an array of constants, but both these alternatives would require constexpr for them to happen on compile-time, while some compilers still do not support constexpr: constexpr not compiling in VC2013

Community
  • 1
  • 1
Serge Rogatch
  • 13,865
  • 7
  • 86
  • 158
1
constexpr double make_double( int64_t v, int64_t man );

write a function that makes a double from a base and mantissa. This can represent every non-special double.

Then write:

template<int64_t v, int64_t man>
struct double_constant;

using the above make_double and various constexpr access methods.

You can even write base and exponent extracting constexpr functions I suspect. Add a macro to remove DRY, or use a variable.


Another approach is:

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

which requires a variable to point to, but is less obtuse.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524