Question

I have some code that looks like this:

// filexd.h
function() {
  // nDim is a compile time constant
  #if DIMENSIONS == 2
    static const int a[nDim] = {1,0};
  #elif DIMENSIONS == 3
    static const int a[nDim] = {2,0,1};
  #else
    #error "Unknown # of dimensions"
  #end
  // do stuff with a...
}

This file is included by others that redefine the macro DIMENSIONS. What I would like to have is something like this:

static const int a[nDim] = (nDim == 2) ? {0,1} : {1,0,2};

I dont want to use a function template specialization or tag dispatch as 99% of the code is identical in both cases. I would like to preserve the same function composition that I already had, and the local semantics of the variable a. So the question is:

How can I avoid using macros in this situation?

Could I use C++1y template variables here? Would the following work?

// filexd.h
function() {

  // The general case should fail somehow, maybe displaying a nice error message.
  template<int nDim> constexpr int stencil[nDim] = throw();
  // And then define the variable for both cases:
  template<> constexpr int stencil<2>[2] = {1, 0};  
  template<> constexpr int stencil<3>[3] = {2, 0, 1};

  static const stencil<nDim> a;
  for(int i = 0; i < nDim; ++i) {
    /// do stuff with a[i]
  }
}

Could I declare the stencil template variable with its specializations inside the function itself?

Was it helpful?

Solution

template<size_t N>
void the_common_work(const int(&arr)[N])
{
    for (size_t i=0; i<N; ++i)
      /// do stuff with arr[i]
}

void function()
{        
    if (nDim == 2)
    {
        int a[2] = {1,0};
        the_common_work(a);
    }
    else if (nDim == 3)
    {
        int a[3] = {2,0,1};
        the_common_work(a);
    }
}

Throw a static_assert in there if you want.

Actually, you don't even have to use a template function here. You could even do this in C.

void the_common_work(const int * arr)
{
    for (size_t i=0; i<nDim; ++i)
        /// do stuff with arr[i]
}

An alternative, in one function:

void function()
{
    const int two[] = {1,0};
    const int three[] = {2,0,1};
    const int *arr;
    if (nDim == 2)
        arr = two;
    else if (nDim == 3)
        arr = three;
    else
    {
        throw something;
    }
    for(int i = 0; i < nDim; ++i) {
        /// do stuff with arr[i]
    }
}

OTHER TIPS

Too long for a comment - what's wrong with a C++11 approach?

#include <array>
#include <cstddef>


template < std::size_t t_dim >
constexpr std::array<int, t_dim> init()
{
    static_assert(false, "Unknown # of dimensions");
    return {};
}

  template <>
  constexpr std::array<int, 2> init<2>()
  {
      return {1, 2};
  }

  template <>
  constexpr std::array<int, 3> init<3>()
  {
      return {1, 2, 3};
  }

void function()
{
    std::array<int, nDim> a = init<nDim>();
}

int main()
{
    function();
}

Edit: approach using local types

Edit2: Using non-type template parameter pack to simplify declaration syntax.

Edit3: Simplified (removing SFINAE was possible after storing the initialization values as non-type template arguments).

Edit4: Combined first and previous approach for even easier usage syntax and allow types which are invalid as non-type template parameter again.

Warning: black ugly template magic

#include <array>
#include <cstddef>
#include <algorithm>
#include <iostream>
#include <iterator>

constexpr std::size_t nDim = 3;

template < typename TArr >
struct stdarray_extent
{
    static std::size_t const value = TArr{}.size();
};

template < bool t_if, typename T_Then, typename T_Else >
struct type_if
{
    using type = T_Then;
};
  template < typename T_Then, typename T_Else >
  struct type_if < false, T_Then, T_Else >
  {
      using type = T_Else;
  };

template < std::size_t t_dim, typename T, typename... TT >
struct init_
    : init_ < t_dim, TT... >
{
    using base = init_ < t_dim, TT... >;
    static bool constexpr valid = (  stdarray_extent<T>::value == t_dim
                                   ? true : base::valid);
    using type = typename type_if < stdarray_extent<T>::value == t_dim,
                                    T, typename base::type
                                  > :: type;

    template < typename TF, typename... TTF >
    static constexpr
    typename std::enable_if< stdarray_extent<TF>::value == t_dim, type >::type
    get(TF&& p, TTF&&... pp)
    {
        return p;
    }

    template < typename TF, typename... TTF >
    static constexpr
    typename std::enable_if< stdarray_extent<TF>::value != t_dim, type>::type
    get(TF&& p, TTF&&... pp)
    {
        return base::get( std::forward<TTF>(pp)... );
    }
};

  template < std::size_t t_dim, typename T >
  struct init_ < t_dim, T >
  {
      static bool constexpr valid = (stdarray_extent<T>::value == t_dim);
      using type = typename type_if < stdarray_extent<T>::value == t_dim,
                                      T,
                                      std::array<typename T::value_type, t_dim>
                                    > :: type;
      template < typename TF >
      static constexpr
      typename std::enable_if< stdarray_extent<TF>::value == t_dim, type>::type
      get(TF&& p)
      {
          return p;
      }

      template < typename TF >
      static constexpr
      typename std::enable_if< stdarray_extent<TF>::value != t_dim, type>::type
      get(TF&& p)
      {
          return {};
      }
  };

template < std::size_t t_dim, typename... TT >
auto init(TT&&... pp)
-> decltype( init_<t_dim, TT...>::get(std::forward<TT>(pp)...) )
{
    static_assert( init_<t_dim, TT...>::valid, "Unknown # of Dimensions" );
    return init_<t_dim, TT...>::get(std::forward<TT>(pp)...);
}


void function()
{
    constexpr std::size_t nDim = 3;

    std::array<int, nDim> a = init<nDim>( std::array<int,2>{{1,2}},
                                          std::array<int,3>{{3,2,1}} );

    // output
    std::copy( std::begin(a), std::end(a),
               std::ostream_iterator<int>{std::cout, ", "} );
}

int main()
{
    function();
}

It's probably overkill in this case, but conversion operators can give a nice way to do this:

#include <array>
template<int i>
void f() {
    struct init {
        constexpr operator std::array<int, 2>() { return { { 1, 0 } }; }
        constexpr operator std::array<int, 3>() { return { { 2, 0, 1 } }; }
    };
    constexpr std::array<int, i> a = init{};
}

You can prevent leaking the name init into the function namespace using a lambda, but then you lose constexpr:

#include <array>
template<int i>
void f() {
    const std::array<int, i> a = [](){
        struct init {
            operator std::array<int, 2>() { return { { 1, 0 } }; }
            operator std::array<int, 3>() { return { { 2, 0, 1 } }; }
        };
        return init{};
    }();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top