6

I have a templated class that can only be instantiated for scalar types (integers, floats, etc.) and I want a member typedef to always be the signed variant of the type. That is:

unsigned int -> signed int
signed long long -> signed long long (already signed)
unsigned char -> signed char
float -> float
long double -> long double
etc...

Unfortunately, std::make_signed only works for integral types, not the floating point types. What is the simplest way to do this? I'm looking for something of the form using SignedT = ...;, to be part of my templated class with template parameter T already guaranteed to be scalar.

4 Answers 4

13

A simple template alias will do:

#include <type_traits>

template<typename T>
struct identity { using type = T; };

template<typename T>
using try_make_signed =
    typename std::conditional<
        std::is_integral<T>::value,
        std::make_signed<T>,
        identity<T>
        >::type;

And this is how you could test it:

int main()
{
    static_assert(::is_same<
        try_make_signed<unsigned int>::type, int
        >::value, "!");

    static_assert(std::is_same<
        try_make_signed<double>::type, double
        >::value, "!");
}

Here is a live example.

Sign up to request clarification or add additional context in comments.

1 Comment

+1, using is so convenient, I need to finaly update my compiler :)
4

@jrok originally had code that could work, only a small adjustment was needed. This is the working code:

template<typename T>
struct YourClass
{
    using SignedT =
        typename std::conditional
        <
            std::is_floating_point<T>::value, //if floating point type
            std::common_type<T>,              //use it as-is
            std::make_signed<T>               //otherwise make sure it is signed
        >::type::type; //notice the double ::type
};

Demo: http://ideone.com/Vw7o82

The above struct could also be modified to itself be a type traits class if this functionality needs to be used multiple times. However, @Andy Prowl's answer does it with an alias template, which is better.

4 Comments

This works, but it has the disadvantage that you have to define it every time. With an alias template like the one in my answer, you could just do using SignedT = typename try_make_signed<T>::type, and you're done.
The class in this answer could easily be refactored to itself be a type traits class, so I don't know what you mean by 'disadvantage' - if anything our answers are the same, right?
Fair enough, you could modify it to make a type trait out of it, yes - even better if done with an alias template, like in my answer
Thank you :) Anyway, interesting use case for std::common_type (although I still believe identity has a better name)
3

After my initial flawed attempt at using std::conditional, I decided to use SFINAE instead. I'm using std::enable_if to conditionaly enable a specialization for floating point types:

template<typename T, typename Enable = void>
struct my_make_signed {
    typedef typename std::make_signed<T>::type type;
};

template<typename T>
struct my_make_signed<T,
    typename std::enable_if<std::is_floating_point<T>::value>::type> {
    typedef T type;
};

9 Comments

Won't that produce an error if make_signed is not specialized for T? (that's the case for floats).
@mfontanini my bad, now it won't. Thanks for the heads up.
You're welcome :D. But now your conditional will produce either std::make_signed<T>(for which applying ::type is OK) or T(which can be float, for example) depending on the result of the condition.
Yeah, i'm fixing it. I'd delete, but can't because it's accepted.
@jrok just change it to typename std::conditional<std::is_floating_point<T>::value, std::common_type<T>, std::make_signed<T>>::type::type
|
0
namespace mine {        

    template<typename T, bool b>
    struct make_signed__ {
        typedef T type;
    };

    template<typename T>
    struct make_signed__<T,false> {
        typedef typename std::make_signed<T>::type type;
    };

    template<typename T>
    struct make_signed {
        typedef typename make_signed__<T, std::is_floating_point<T>::value>::type type;
    };

}

int main () {
    std::cout << std::is_same<mine::make_signed<unsigned int>::type, int>::value;
    std::cout << std::is_same<mine::make_signed<long double>::type, long double>::value;
}

5 Comments

That's not very portable because it can't support arbitrarily supported types (e.g. a compiler could support std::uint512_t and client code could instantiate my class with that)
@LB-- Yes that is true. But he only has to define what he uses. By the way does std::make_signed support those?
yes, it must because it is defined by the compiler implementation.
@LB-- Edited. I think it is very portable now.
It could do without the macros, then ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.