user14717
user14717

Reputation: 5151

Compile-time array selection in C++

I have a class which holds an array (for a filter) based on compile time parameters. For example:

template<class Real, unsigned N>
class foo {
public:
  // stuff ...
private:
   static const constexpr std::array<Real, unsigned N> m_h;
};

For instance, if N=4 and Real = double, I would like m_h to be (say):

m_h = {0.4829629131445341,  0.8365163037378079,  0.2241438680420133, -0.129409522551260};

and if N=4 and Real = float, I'd like

m_h = {0.4829629f, 0.8365163f, 0.2241438f, -0.1294095f};

If N=6, and Real=double, I'd like the numbers to be totally different:

m_h = {0.332670552950082615998, 0.806891509311092576494, 0.45987750211849157009, -0.1350110200102545886963899, -0.0854412738820266616928191, 0.0352262918857095366027};

What is the most elegant syntax to achieve this goal? The closest I have found is in Boost's Gaussian quadrature, which achieves the goal by first classifying the Real type by number of digits, and convertibility to float, double, and long double. Then it introduces a foo_detail class which gives functions get_constants() and selects the constants required at runtime. Boost of course supports many compilers and their varying C++11 capability, so I feel like there might be a more expressive solution using (say) C++17.

Upvotes: 1

Views: 139

Answers (1)

max66
max66

Reputation: 66200

Not sure I understand what exactly you want but... I suppose you can initialize m_h calling a constexpr template function and fully specialize it.

I mean... you can write foo() as follows

template <typename Real, std::size_t N>
class foo
 {
   private:
      static constexpr std::array<Real, N> m_h { bar<Real, N>() };
 };

template <typename Real, std::size_t N>
constexpr std::array<Real, N> foo<Real, N>::m_h;

and write a set of bar() template functions as follows

template <typename Real, std::size_t N>
constexpr std::array<Real, N> bar ();

template <>
constexpr std::array<double, 4u> bar<double, 4u> ()
 { return { {0.4829629131445341,  0.8365163037378079,
             0.2241438680420133, -0.129409522551260} }; }

template <>
constexpr std::array<float, 4u> bar<float, 4u> ()
 { return { {0.4829629f, 0.8365163f, 0.2241438f, -0.1294095f} }; }

template <>
constexpr std::array<double, 6u> bar<double, 6u> ()
 { return { { 0.332670552950082615998,      0.806891509311092576494,
              0.45987750211849157009,      -0.1350110200102545886963899,
             -0.0854412738820266616928191,  0.0352262918857095366027} }; }

// as many `bar()` specializations as you want

The following is a full compiling example (with a simplified foo)

#include <array>
#include <iostream>

template <typename Real, std::size_t N>
constexpr std::array<Real, N> bar ();

template <>
constexpr std::array<double, 4u> bar<double, 4u> ()
 { return { {0.4829629131445341,  0.8365163037378079,
             0.2241438680420133, -0.129409522551260} }; }

template <>
constexpr std::array<float, 4u> bar<float, 4u> ()
 { return { {0.4829629f, 0.8365163f, 0.2241438f, -0.1294095f} }; }

template <>
constexpr std::array<double, 6u> bar<double, 6u> ()
 { return { { 0.332670552950082615998,      0.806891509311092576494,
              0.45987750211849157009,      -0.1350110200102545886963899,
             -0.0854412738820266616928191,  0.0352262918857095366027} }; }

template <typename Real, std::size_t N>
struct foo
 {
   static constexpr std::array<Real, N> m_h { bar<Real, N>() };
 };

template <typename Real, std::size_t N>
constexpr std::array<Real, N> foo<Real, N>::m_h;

int main ()
 {
   for ( auto f : foo<double, 4u>::m_h )
      std::cout << f << ' ';

   std::cout << std::endl;

   for ( auto f : foo<float, 4u>::m_h )
      std::cout << f << ' ';

   std::cout << std::endl;

   for ( auto f : foo<double, 6u>::m_h )
      std::cout << f << ' ';

   std::cout << std::endl;
 }

Or maybe, if you don't want to develop bar() through full specialization, you can write a single bar() function using a lot of if constexpr as follows

template <typename Real, std::size_t N>
constexpr std::array<Real, N> bar ()
 {
   if constexpr ( std::is_same<long double, Real>::value )
    {
      if constexpr ( 4u == N )
         return { /* something */ };
      else if constexpr ( 6u == N )
         return { /* something */ };
      // else if constexpr ( ?? == N ) ...
    }
   else if constexpr ( std::is_same<double, Real>::value )
    { 
      if constexpr ( 4u == N )
         return { {0.4829629131445341,  0.8365163037378079,
                   0.2241438680420133, -0.129409522551260} }; 
      else if constexpr ( 6u == N )
         return { { 0.332670552950082615998,      0.806891509311092576494,
                    0.45987750211849157009,      -0.1350110200102545886963899,
                   -0.0854412738820266616928191,  0.0352262918857095366027} }; 
      // else if constexpr ( ?? == N ) ...
    }
   else if constexpr ( std::is_same<float, Real>::value )
    { 
      if constexpr ( 4u == N )
         return { {0.4829629f, 0.8365163f, 0.2241438f, -0.1294095f} };
      else if constexpr ( 6u == N )
         return { /* something */ };
      // else if constexpr ( ?? == N ) ...
    }
 }

Upvotes: 3

Related Questions