Samir
Samir

Reputation: 15

compile-time function choice depending on type size

I would like to have a template function to copy data in a special way. There is an easy way if the data element type size is a multiple of 4 Bytes, i.e., (sizeof(T) % 4 == 0):

template <typename T, typename Idx, uint32 dimensions>
void loadData4BWords(T *target, const T *source, const Idx eleCount);

and there is a more complex way to copy the array if that is not the case:

template <typename T, typename Idx, uint32 dimensions>
void loadDataNo4BWords(T *target, const T *source, const Idx eleCount);

How do I write a caller template function that makes this decision at compile time and transparent to the user? For example:

template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount);

which is supposed to call one of the two above versions depending on the compile time condition multipleOf4BWord = (sizeof(T) % 4 == 0). More accurately, loadData is supposed to be translated to one of the two above versions at compile time.

Upvotes: 0

Views: 535

Answers (4)

max66
max66

Reputation: 66200

In C++17 if constexpr, as suggested by uneven_mark, is the simplest and clearer solution (IMHO).

Before C++17 (C++11 and C++14) you can use overloading and SFINAE (with std::enable_if)

I mean... you can simplify a lot the problem if, instead a loadData4BWords() and a loadDataNo4BWords() functions ever enabled, you create a loadData() enabled only when 0u == sizeof(T) % 4u (loadData4BWords() equivalent) and a loadData() enabled only when 0u != sizeof(T) % 4u (loadDataNo4BWords() equivalent).

The following is a full C++11 working example (simplified: only one parameter)

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<0u == sizeof(T) % 4u>::type loadData (T *)
 { std::cout << "4 version" << std::endl; }

template <typename T>
typename std::enable_if<0u != sizeof(T) % 4u>::type loadData (T *)
 { std::cout << "no 4 version" << std::endl; }


int main ()
 {
   char  ch;
   int   i;

   loadData(&ch);
   loadData(&i);
 }

In C++14 (and C++17, if you want) you can use std::enable_if_t to simplify a little

template <typename T>
std::enable_if_t<0u == sizeof(T) % 4u> loadData (T *)
 { std::cout << "4 version" << std::endl; }

template <typename T>
std::enable_if_t<0u != sizeof(T) % 4u> loadData (T *)
 { std::cout << "no 4 version" << std::endl; }

p.s.: see also the tag dispatching way in Jeff Garrett's answer.

Upvotes: 0

Jeff Garrett
Jeff Garrett

Reputation: 7383

if constexpr is nicest. But old school tag dispatch also works, and maybe it could be clearer in some instances (particularly pre-C++17), so I'll contribute this option to the discussion:

template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount, std::true_type)
{
    loadData4BWords(target, source, eleCount);
}

template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount, std::false_type)
{
    loadDataNo4BWords(target, source, eleCount);
}

template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount)
{
    loadData(target, source, eleCount,
        std::integral_constant<bool, sizeof(T) % 4 == 0>{});
}

Upvotes: 1

SoronelHaetir
SoronelHaetir

Reputation: 15164

You could make the function a member of a struct and use partial template specialization:

template<typename T, bool is_even_multiple=0==(sizeof(T)%4)>
struct load_data_helper;

template<typename T>
struct load_data_helper<T, true>
{
template<uint32_t Dimensions, typename Idx>
static void apply(T * dest, T const * src, Idx const & index)
{ ... }
};
template<typename T>
struct load_data_helper<T, false>
{
template<uint32_t Dimensions, typename Idx>
static void apply(T * dest, T const * src, Idx const & index)
{ ... }
};

template<uint32_t Dimensions, typename T, typename Idx>
void load_data(T * dest, T const * src, Idx const & index)
{
load_data_helper<T>::apply<Dimensions>(dest, src, index);
}

and then the call would be:

load_data<3>(dest, src, index);

Note I have not actually compiled the above so there may be errors in the provided code but the outlined method should work.

Upvotes: 0

walnut
walnut

Reputation: 22152

You can use if constexpr since C++17 to call one or the other:

template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount) {
    if constexpr(sizeof(T) % 4 == 0)
        loadData4BWords<T, Idx, dimensions>(target, source, eleCount);
    else
        loadDataNo4BWords<T, Idx, dimensions>(target, source, eleCount);
}

In contrast to if, if constexpr is tested at compile-time and only the matching branch is compiled.

Upvotes: 4

Related Questions