C++11 way to write template for picking bigger integer type?
Asked Answered
S

3

11

At compile time in C++11 in a template function that takes 2 template parameters, both of which must be unsigned integer types, I'd like to have a local variable have the type of whichever of the two template parameters has more bits. In C++03 I might write something like:

template<bool, class T, class U>
struct pick_first;

template<class T, class U>
struct pick_first<true, T, U> {
    typedef T type;
};

template<class T, class U>
struct pick_first<false, T, U> {
    typedef U type;
};

template<class T, class U>
struct pick_bigger {
    typedef typename pick_first<(sizeof(T) >= sizeof(U)), T, U>::type type;
};

// usage
template<class uintX_t, class uintY_t>
void foo() {
    typename pick_bigger<uintX_t, uintY_t>::type mylocal = 0;
    // insert doing stuff with mylocal here
}

Can I leverage any of the new C++11 features to make this simpler? I know I could use variadic templates to make it work with more than just pairs of types, and instead of using pick_first I could write lots of specializations to make it work with the new int_leastX_t and int_fastX_t types. But I'm curious if there's just a plain better approach to this. Maybe somehow leveraging auto/constexpr/decltype?

Spanjian answered 25/9, 2012 at 3:40 Comment(6)
Have you considered std::common_typeSayers
I had not heard of std::common_type! Very interesting. This would work for me. You should post as an answer though so I can vote you up ;)Spanjian
@DavidRodríguez-dribeas But common_type will not always work due to integer promotion rules. For instance, std::common_type<short,char>::type is int, which may be larger than either of the 2 types.Pirn
The pick_first template seems to be aptly named.Interrogation
@Cheersandhth.-Alf: Oops, fixed.Spanjian
if a type wider than both is necessary (to avoid overflow) then see C++ integer type twice the width of a given typeUncaredfor
U
11

Your pick_first is just std::conditional in C++11, so you could write

template<class T, class U>
struct wider {
    using type = typename std::conditional<sizeof(T) >= sizeof(U), T, U>::type; // I'm using the C++11 type alias feature 1) to educate people about them and 2) because I like them better than typedefs.
};

If you just want a type suitable for holding the result of some expression involving both types and don't necessarily need exactly one of the two types then std::common_type, or perhaps auto, is the best solution:

template<class uintX_t, class uintY_t>
void foo() {
    typename std::common_type<uintX_t, uintY_t>::type mylocal = 0;
    // insert doing stuff with mylocal here
}

// or
template<class uintX_t, class uintY_t>
void foo(uintX_t x, uintY_t y) {
    auto mylocal = x + y;
}

and your implementation of pick_bigger is missing a typename in there: typedef typename pick_first<(sizeof(T) >= sizeof(U)), T, U>::type type;

Unclench answered 25/9, 2012 at 5:9 Comment(4)
This adds convoluted template magic, with code that doesn't compile with the most used compiler, in order to produce a sub-optimal type for the local variable that the OP wants a type for. That is extremely silly. But it does look clever.Interrogation
Wow, wasn't aware of std::conditional either, sweet.Spanjian
@JosephGarvin You might look at en.cppreference.com/w/cpp/types to see all the new type traits.Unclench
@bames53: Yeah, I just read over those. I didn't realize any type_traits were added that weren't already in boost around the time of tr1, now I'm going over all sections there looking at the C++11 tagged pieces.Spanjian
B
3

This is my solution that can select the widest from N types up to the template recursion limit.

I've also included the code for narrowest.

template <typename TFirst, typename... TOther>
struct widest {
private:
  using rhs_recursive_type = typename widest<TOther...>::type;

public:
  using type =
      typename std::conditional<sizeof(TFirst) >= sizeof(rhs_recursive_type),
                                TFirst, rhs_recursive_type>::type;
};

template <typename TFirst>
struct widest<TFirst> {
  using type = TFirst;
};

template <typename TFirst, typename... TOther>
struct narrowest {
private:
  using rhs_recursive_type = typename widest<TOther...>::type;

public:
  using type =
      typename std::conditional<sizeof(TFirst) <= sizeof(rhs_recursive_type),
                                TFirst, rhs_recursive_type>::type;
};

template <typename TFirst>
struct narrowest<TFirst> {
  using type = TFirst;
};
Bos answered 31/8, 2021 at 13:55 Comment(0)
I
2

since both types are unsigned, just do decltype( T1() + T2() ).

Interrogation answered 25/9, 2012 at 4:19 Comment(2)
This has the same problem with integer promotion as common_type, decltype(char()+short()) is int. If that's acceptable then just using common_type directly is the best solution.Unclench
@barnes54: common_type adds a header dependency. there's no need for that. as for getting unsigned int instead short, the OP is asking for a suitable type for a local variable. the most suitable type is the one able to store expression results directly without conversion. that is what this expression produces. and that most suitable type is not what a silly over-engineered templated selector produces.Interrogation

© 2022 - 2024 — McMap. All rights reserved.