Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 518
It'sPete Avatar asked Jun 02 '26 12:06

It'sPete


2 Answers

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.

like image 75
SergeyA Avatar answered Jun 04 '26 11:06

SergeyA


Here's a way to determine at compile time whether the inputs match the requirements, and also whether they require a conversion or not (assuming same type means no conversion.) I changed your meta-function IsSample to use a boolean template variable, because I think it's cleaner.

First example I use function overloading to handle the case where both src and dest are the same type. When they are, that function can noop, and when they aren't you can convert. This makes it a compile time if rather than a runtime if.

template <typename T>
constexpr bool is_sample = std::is_floating_point<T>::value || std::is_integral<T>::value;

template <typename T1, typename T2,  typename=std::enable_if_t<is_sample<T1> && is_sample<T2>>>
void convertSamples(T1* dst, const T2* src, size_t num_samples, double scalar) {
    std::cout << "converting...\n";
}

template <typename T,  typename=std::enable_if_t<is_sample<T>>>
void convertSamples(T* dst, const T* src, size_t num_samples, double scalar) {
    std::cout << "NOT converting (noop)\n";
}

You can also conveniently consolidate these with an c++17 "if constexpr" which will be approximately the same as above, but in a single function.

template <typename T1, typename T2, typename = std::enable_if_t<is_sample<T1> && is_sample<T2>>>
void convertSamples(T1* dst, const T2* src, size_t num_samples, double scalar) {
    if constexpr (std::is_same_v<T1, T2>) {
        std::cout << "not converting\n";
        return;
    }

    std::cout << "converting\n";
}
like image 28
Chris Uzdavinis Avatar answered Jun 04 '26 10:06

Chris Uzdavinis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!