C++ template trait to specify any unsigned integral type
Asked Answered
R

2

6

I’m trying to implement a function which accepts only unsigned integral types. Below is what I’ve tried so far. It works for "unsigned int", but why doesn’t this compile for an "unsigned short?"

#include <iostream>
#include <type_traits>

template<typename T, class = typename std::enable_if<std::is_unsigned<T>::value>::type>
inline const T oddProduct(T n) noexcept {
    return (n <= 1) ? n : (n % 2) ? oddProduct(n - 2)*n : oddProduct(--n);
}

int main() {
    std::cout << "Product of odd integers from 1 to 15: " << oddProduct<unsigned short>(15) << std::endl;

    return 0;
}

Note: MSVS 2017 Community C++14 options.

Rooks answered 22/1, 2018 at 19:31 Comment(0)
J
5

The thing is that due to the integral promotion:

n - 2

has a type of int which is not unsigned.

You might try to add static_cast:

template<typename T, class = typename std::enable_if<std::is_unsigned<T>::value>::type>
inline const T oddProduct(T n) noexcept {
    return (n <= 1) ? n : (n % 2) ? oddProduct(static_cast<T>(n - 2))*n : oddProduct(--n);
                                               ^^^^^^^^^^^^^^
}

so n - 2 is casted to unsigned short when you call oddProduct<unsigned short>.

Another possible option is to change 2 to 2U.


Also note, that, once you use C++14, you might use std::enable_if_t:

class = typename std::enable_if_t<std::is_unsigned<T>::value>

DEMO

Jolda answered 22/1, 2018 at 19:34 Comment(2)
Also note that since C++17, you can replace std::is_unsigned<T>::value with std::is_unsigned_v<T>.Baggett
@Qu'est-cet'yont yup, but OP has explicitly specified C++14Euphemism
A
0

Edgar Rokyan's answer addresses the integral promotion issue in OP's code, but there is another problem reguarding the return value type and the possible overflow of the calculations.

As a matter of fact, considering the example provided in the question, the "product of odd integers from 1 to 15" is 2027025, a value that requires more then 16 bits (the size of an unsigned short in most systems), so that letting the function return the same type of its parameter would lead to wrong results.

#include <iostream>
#include <type_traits>
#include <stdexcept>

template<typename T>
inline const auto oddProduct(T n)  noexcept 
-> std::enable_if_t<std::is_unsigned<T>::value, unsigned long long> {
    return n < T{2}
        ? n
        : (n % T{2})
            ? oddProduct<T>(n - T{2})*n
            : oddProduct(--n);
}

template<typename T>
inline const auto oddProduct(T n) 
-> std::enable_if_t<std::is_signed<T>::value, unsigned long long> {
    if ( n < 0 ) throw std::domain_error("Negative value passed");
    return n < T{2}
        ? n
        : (n % T{2})
            ? oddProduct<std::make_unsigned_t<T>>(n - T{2})*n
            : oddProduct<std::make_unsigned_t<T>>(--n);
}

int main() {
    unsigned char n0 {15};
    std::cout << "Product of odd integers from 1 to 15: " << oddProduct(n0) << '\n';
    unsigned short n1 {15};
    std::cout << "Product of odd integers from 1 to 15: " << oddProduct(n1) << '\n';
    unsigned n2 {15};
    std::cout << "Product of odd integers from 1 to 15: " << oddProduct(n2) << '\n';
    short n3 {15};
    std::cout << "Product of odd integers from 1 to 15: " << oddProduct(n3) << '\n';  
}

In my proposal, the function always returns an unsigned long long. I also added an overload to deal with signed types.

Abby answered 22/1, 2018 at 21:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.