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();
};