André Wagner André Wagner - 4 months ago 14
C++ Question

How can I create a constexpr function that returns a type (to be used in a template parameter)

I'm looking for some way to create a class, with a template parameter type, based on a template parameter number.

What I'm trying to do is something like this:

template<size_t n>
constexpr auto type_from_size() {
if(n < 256) {
return uint8_t;
} else {
return uint16_t;
}
}

template<size_t n>
class X {
type_from_size<n>() t;
}

X<500> x;
x.t = 500;


So, in the code above, the
constexpr
function
type_from_size()
would receive the number 500 and would return the type
uint16_t
, and this would be the type of the member
X.t
.

I know this is obviously terrible code, but is this possible using templates?

Answer

A function cannot return a type. You should use a template.

For a selection between only two types, the built-in std::conditional is sufficient.

#include <type_traits>
#include <cstdint>

template <size_t n>
using type_from_size = typename std::conditional<(n < 256), uint8_t, uint16_t>::type;
// ^ if `n < 256`, the ::type member will be typedef'ed to `uint8_t`.
//                 otherwise, it will alias to `uint16_t`.
//   we then give a convenient name to it with `using`.

template <size_t n>
struct X {
    type_from_size<n> t;
    // ^ use the template
};

If you need to support more than two values, you can change multiple conditional together like an if/else if/else chain, but OH MY EYES

template <size_t n>
using type_from_size =
    typename std::conditional<(n <= 0xff), uint8_t,
        typename std::conditional<(n <= 0xffff), uint16_t,
            typename std::conditional<(n <= 0xffffffff), uint32_t,
                uint64_t
            >::type
        >::type
    >::type;

You could also use specialization together with std::enable_if (SFINAE) to make it more "low-level":

template <size_t n, typename = void>
struct type_from_size_impl;
// Declare a "size_t -> type" function.
//  - the `size_t n` is the input
//  - the `typename = void` is a placeholder
//    allowing us to insert the `std::enable_if` condition.

template <size_t n>
struct type_from_size_impl<n, typename std::enable_if<(n <= 0xff)>::type> {
    using type = uint8_t;
};
// We add a partial specialization
//  - in `std::enable_if<c>::type`, if `c` is true, `::type` will be typedef'ed to `void`
//  - otherwise, `::type` will not be defined.
//  - if `::type` is not defined, substitution failed,
//    meaning we will not select this specialization

template <size_t n>
struct type_from_size_impl<n, typename std::enable_if<(n > 0xff && n <= 0xffff)>::type> {
    using type = uint16_t;
};

template <size_t n>
struct type_from_size_impl<n, typename std::enable_if<(n > 0xffff && n <= 0xffffffff)>::type> {
    using type = uint32_t;
};

template <size_t n>
struct type_from_size_impl<n, typename std::enable_if<(n > 0xffffffff)>::type> {
    using type = uint64_t;
};

template <size_t n>
using type_from_size = typename type_from_size_impl<n>::type;
// Here we want to find a specialization of `type_from_size_impl<n>`
// All 4 specializations will be tried.
// If only one specialization works, we will use that one
// (Which is why we need to ensure the ranges are not overlapping
//  otherwise the compiler will complain)
// Then we take the `::type` out the complete this "type-level function".