Home > Software design >  Specialize struct template depending on signed-ness of char
Specialize struct template depending on signed-ness of char

Time:04-12

I have a function template<typename T> void frobnicate() that does stuff. I need T to be one of a few select types, and I need some information about those types. I do this by providing traits:

template<typename T_Raw>
struct FrobnicationTraits
{
        static constexpr bool isValidFrobnicationType = false;
};

template<>
struct FrobnicationTraits<unsigned char>
{
        static constexpr bool isValidFrobnicationType = true;
        static constexpr bool isUnsigned = true;
        static constexpr unsigned char minValue = 0;
        static constexpr unsigned char maxValue = 255;
        //Elided in each specialization: some other values that are needed by frobnicate()
};

template<>
struct FrobnicationTraits<signed char>
{
        static constexpr bool isValidFrobnicationType = true;
        static constexpr bool isUnsigned = false;
        static constexpr signed char minValue = -128;
        static constexpr signed char maxValue =  127;
};

//Elided: versions for unsigned short and signed short

And then:

template<typename T, typename = typename std::enable_if<FrobnicationTraits<T>::isValidFrobnicationType>::type>
void frobnicate()
{
    using Traits = FrobnicationTraits<T>;
    auto promotedMinValue =  Traits::minValue;
    auto promotedMaxValue =  Traits::maxValue;
    std::cout << "Frobnicating with " << (Traits::isUnsigned ? "unsigned" : "  signed") << " type of size " << sizeof(T) <<" between " << promotedMinValue << " and " << promotedMaxValue << std::endl;
    //...
}

This works fine for these calls:

int main()
{
    frobnicate<unsigned char>();  // Prints "Frobnicating with unsigned type of size 1 between 0 and 255"
    frobnicate<  signed char>();  // Prints "Frobnicating with   signed type of size 1 between -128 and 127"
    frobnicate<unsigned short>(); // Prints "Frobnicating with unsigned type of size 2 between 0 and 65535"
    frobnicate<  signed short>(); // Prints "Frobnicating with   signed type of size 2 between -32768 and 32767"
    //frobnicate<unsigned int>(); // Correctly fails to compile, because the enable_if is not satisfied, since FrobnicationTraits<unsigned int> uses the default FrobnicationTraits where isValidFrobnicationType is false
    return 0;
}

But now I want to be able to correctly frobnicate<char>().
char is implementation-defined to be signed or unsigned, but it's always considered to be a different type than unsigned char and signed char. So simply calling frobnicate<char>() fails to compile, because it won't pick any of the specialized FrobnicationTraits.
I can add one specialization manually:

template<>
struct FrobnicationTraits<char>
{
        static constexpr bool isValidFrobnicationType = true;
        static constexpr bool isUnsigned = true;
        static constexpr char minValue = 0;
        static constexpr char maxValue = 255;
};

But I can't know ahead of time whether this is the correct one. In fact, I happened to pick the wrong values, so this prints "Frobnicating with unsigned type of size 1 between 0 and -1".

I would like to effectively do this:

template<> // I don't know where to put the `std::enable_if<>` here, but use this if char is unsigned
struct FrobnicationTraits<char>
{
        static constexpr bool isValidFrobnicationType = true;
        static constexpr bool isUnsigned = true;
        static constexpr char minValue = 0;
        static constexpr char maxValue = 255;
};

template<> // I don't know where to put the `std::enable_if<>` here, but use this if char is signed
struct FrobnicationTraits<char>
{
        static constexpr bool isValidFrobnicationType = true;
        static constexpr bool isUnsigned = false;
        static constexpr char minValue = -128;
        static constexpr char maxValue =  127;
};

Ideally the solution should not use the preprocessor.

CodePudding user response:

There is std::is_signed that can tell you wether char is signed or not. If you need not distinguish between char and signed char / unsigned char you can reuse the traits by inheriting from either of them via std::conditional:

template<>
struct FrobnicationTraits<char> : FrobnicationTraits<
                                      std::conditional_t<         
                                          std::is_signed_v<char>, 
                                          signed char,
                                          unsigned char
                                      >
                                  >
{};

Perhaps use a helper:

using signed_or_unsigned_char = std::conditional_t<std::is_signed_v<char>,signed char, unsigned char>;

then inherit FrobnicationTraits<char> from FrobnicationTraits<signed_or_unsigned_char>.

PS: Note that some of your traits functionality is already covered by std::numerical_limits. If you use std::is_signed directly, all you actually need in the trait is isValidFrobnicationType.

CodePudding user response:

You can use templates to reduce repetitive code, and automatically calculate isUnsigned, minValue and maxValue through std::numerical_limits<T>

#include <limits>

template<class T>
constexpr inline bool isValidFrobnicationType = false;
// specialization for valid types
template<>
constexpr inline bool isValidFrobnicationType<signed char> = true;
// ...

template<typename T, bool = isValidFrobnicationType<T>>
struct FrobnicationTraits;

template<typename T>
struct FrobnicationTraits<T, false> {
  static constexpr bool isValidFrobnicationType = false;
};

template<typename T>
struct FrobnicationTraits<T, true> {
  static constexpr bool isValidFrobnicationType = true;
  static constexpr bool isUnsigned = !std::numeric_limits<T>::is_signed;
  static constexpr auto minValue =    std::numeric_limits<T>::min();
  static constexpr auto maxValue =    std::numeric_limits<T>::max();
};

Demo

  • Related