Search code examples
c++c++11templatestypechecking

std::enable_if type checking


I am trying to write a function that can convert a buffer of type T2 into a buffer of type T1. Much of the code that sits around this function is very C-like, so I have to accept raw pointers to the buffers. The buffers contain digital samples. For floating point buffers (float, double, etc.) the smallest possible value is -1.0 and the largest value is 1.0. For integer types, the samples span the range of the type. For example, int16_t samples run from -32,768 to 32,767. As a result, there is more going on than a type conversion, usually a multiplication with a scaling factor is needed to get the samples into the correct range.

In the past, I have encountered libraries that write specialized conversions for each possible sample type. I'll be honest and admit that I come from an embedded background and I would normally do the same (I'm trying to learn more modern C++ techniques and not just treating the language as "C with classes"), however I can see that this approach leads to a good amount of code that's copy-pasted and only slightly edited.

So far I think my templated approach seems to work, but I would like to enforce my type checks with std::enable_if at compile time (I want the compiler to error out if someone tries to convert types that aren't used for samples). The code I have so far is reproduced below.

template <class T>
struct is_sample_type : std::integral_constant <
    bool,
    (std::is_floating_point<T>::value || std::is_integral<T>::value)> {};

template <typename T1, typename T2>
void convertSamples(T1* const dst, const T2* const src,
                    const size_t num_samples, const double scalar) {
  if ((!is_sample_type<T1>::value) || (!is_sample_type<T2>::value))
    throw std::invalid_argument("Invalid sample type passed in.");
  if (std::is_same<T1, T2>::value) return;  // nothing to convert

  // Do conversion...
}

I have several questions:

  1. How can I convert the logic inside convertSamples() to perform the checks at compile time using std::enable_if? I want to error out at compile time if the types passed in are the same or if either is not a type used for samples. I have tried to follow many examples, but the syntax still feels foreign to me.

  2. Does std::is_integral return true for the integer types defined in stdint.h? I figured this is true since these are just aliases.

  3. What is the compiler doing under the hood to "fill in" the type and when does this happen? Namely, I am trying to use OpenMP to perform the conversion steps using SIMD instructions, so I'm hoping that the template type is selected before any other optimizations are done.


Solution

  • Answers:

    1. Very straightforward:

      static_assert(is_sample_type<T1>::value && is_sample_type<T2>::value), "Please use sample types");
      
    2. Yes, returns true. See https://en.cppreference.com/w/cpp/types/is_integral

    3. Function is created (we say 'template is instantiated') for specific type, and then optimized normally.