Does there exist a lazy equivalent of std::conditional in C++? - c++

Upon calling std::contional<bool, T1, T2> both the types T1 and T2 get instantiated as soon as its called, irrespective of the value of the bool, does there exist an implementation (or some pointers to implement it) where only the relevant type is instantiated.
Following code for example is impossible to write using std::conditional. We can't even use SFINAE over structs (get in the example).
struct Term {};
template <int N, typename T, typename ...Ts>
struct PairList
{
static constexpr int i = N;
using type = T;
using tail = PairList<N + 1, Ts...>;
};
template <int N, typename T>
struct PairList<N, T>
{
static constexpr int i = N;
using type = T;
using tail = Term;
};
template <int N, typename pairList>
struct get
{
static constexpr auto good = (N == pairList::i);
using T = typename std::conditional<
good,
typename pairList::type,
typename get<N, typename pairList::tail>::T>::type; // The second parameter might not be defined
};

You can use if constexpr to lazily instantiate templates while keeping familiar code structure:
// C++20: std::type_identity
template<typename T>
struct type_t {
using type = T;
};
template <int N, typename pairList>
struct get
{
static auto choose_type() {
if constexpr (N == pairList::i) {
return type_t<typename pairList::type>{};
} else {
return type_t<typename get<N, typename pairList::tail>::T>{};
}
}
using T = typename decltype(choose_type())::type;
};

I usually use template specialization to avoid this sort of problem
// bool is false -> recursion
template <int N, typename pairList, bool = N == pairList::i>
struct get : public get<N, typename pairList::tail>
{ };
// bool is true -> stop recursion and type is selected
template <int N, typename pairList>
struct get<N, pairList, true>
{ using T = typename pairList::type; };
The following is a full compiling example
#include <type_traits>
struct Term {};
template <int N, typename T, typename ...Ts>
struct PairList
{
static constexpr int i = N;
using type = T;
using tail = PairList<N + 1, Ts...>;
};
template <int N, typename T>
struct PairList<N, T>
{
static constexpr int i = N;
using type = T;
using tail = Term;
};
// bool is false -> recursion
template <int N, typename pairList, bool = N == pairList::i>
struct get : public get<N, typename pairList::tail>
{ };
// bool is true -> stop recursion and type is selected
template <int N, typename pairList>
struct get<N, pairList, true>
{ using T = typename pairList::type; };
int main ()
{
using T0 = PairList<0u, int, long, long long>;
using T1 = get<1u, T0>::T;
static_assert( std::is_same_v<T1, long>, "!" );
}

Related

std::is_same_v with unspecialized template

I have the following code:
#include <iostream>
#include <type_traits>
using namespace std;
template<typename T, int N>
class A {
public:
static constexpr int n = N;
};
template<typename T, typename X, int N>
class B {
public:
static constexpr int x = N;
};
template<typename T>
void test(T) {
if constexpr (std::is_same_v<T, A>) {
int a = T::n;
} else if constexpr (std::is_same_v<T, B>) {
int a = T::x;
}
cout << "a";
};
int main()
{
A<int, 2> a;
test(a);
return 0;
}
Compiling it produces the following error:
error: type/value mismatch at argument 2 in template parameter list for ‘template<class _Tp, class _Up> constexpr const bool std::is_same_v<_Tp, _Up>’
20 | if constexpr (std::is_same_v<T, A>) {
| ~~~~~^~~~~~~~~~~~~~~
note: expected a type, got ‘A’
The Problem is that I cannot use a correct type here (like A), since I don't know what the template parameters are or how many there are. Basically I want to match any type of class A with any template arguments.
You need to write your own trait for that:
template<typename>
struct is_specialization_of_A : std::false_type {};
template<typename T, int N>
struct is_specialization_of_A<A<T,N>> : std::true_type {};
template<typename T>
inline constexpr auto is_specialization_of_A_v = is_specialization_of_A<T>::value;
and analogously for B.
You could take the template as template template parameter of the trait as well, so that you only need one trait definition, but that only works as long as the template parameter categories of the templates match. This is not the case here (A has <type, non-type>, while B has <type, type, non-type>).
template<typename, template<typename, auto> class>
struct is_specialization_of : std::false_type {};
template<template<typename, auto> class Tmpl, typename T, auto N>
struct is_specialization_of<Tmpl<T, N>, Tmpl> : std::true_type {};
template<typename T, template<typename, auto> class Tmpl>
inline constexpr auto is_specialization_of_v = is_specialization_of<Tmpl, T>::value;
This can be used as is_specialization_of_v<A, T>, but not is_specialization_of_v<B, T>.
If you have many class templates like A and B, you can define a single type trait that would return a "tag":
struct tag_A {};
struct tag_B {};
template<class>
struct get_tag {
using type = void;
};
template<typename T, int N>
struct get_tag<A<T, N>> {
using type = tag_A;
};
template<typename T, typename X, int N>
struct get_tag<B<T, X, N>> {
using type = tag_B;
};
template<typename T>
void test(T) {
using tag = typename get_tag<T>::type;
if constexpr (std::is_same_v<tag, tag_A>) {
// ...
} else if constexpr (std::is_same_v<tag, tag_B>) {
// ...
}
}

getting a value for any type in a constexpr environment without default constructibility [duplicate]

Is there a utility in the standard library to get the index of a given type in std::variant? Or should I make one for myself? That is, I want to get the index of B in std::variant<A, B, C> and have that return 1.
There is std::variant_alternative for the opposite operation. Of course, there could be many same types on std::variant's list, so this operation is not a bijection, but it isn't a problem for me (I can have first occurrence of type on list, or unique types on std::variant list).
Update a few years later: My answer here may be a cool answer, but this is the correct one. That is how I would solve this problem today.
We could take advantage of the fact that index() almost already does the right thing.
We can't arbitrarily create instances of various types - we wouldn't know how to do it, and arbitrary types might not be literal types. But we can create instances of specific types that we know about:
template <typename> struct tag { }; // <== this one IS literal
template <typename T, typename V>
struct get_index;
template <typename T, typename... Ts>
struct get_index<T, std::variant<Ts...>>
: std::integral_constant<size_t, std::variant<tag<Ts>...>(tag<T>()).index()>
{ };
That is, to find the index of B in variant<A, B, C> we construct a variant<tag<A>, tag<B>, tag<C>> with a tag<B> and find its index.
This only works with distinct types.
I found this answer for tuple and slightly modificated it:
template<typename VariantType, typename T, std::size_t index = 0>
constexpr std::size_t variant_index() {
static_assert(std::variant_size_v<VariantType> > index, "Type not found in variant");
if constexpr (index == std::variant_size_v<VariantType>) {
return index;
} else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) {
return index;
} else {
return variant_index<VariantType, T, index + 1>();
}
}
It works for me, but now I'm curious how to do it in old way without constexpr if, as a structure.
You can also do this with a fold expression:
template <typename T, typename... Ts>
constexpr size_t get_index(std::variant<Ts...> const&) {
size_t r = 0;
auto test = [&](bool b){
if (!b) ++r;
return b;
};
(test(std::is_same_v<T,Ts>) || ...);
return r;
}
The fold expression stops the first time we match a type, at which point we stop incrementing r. This works even with duplicate types. If a type is not found, the size is returned. This could be easily changed to not return in this case if that's preferable, since missing return in a constexpr function is ill-formed.
If you dont want to take an instance of variant, the argument here could instead be a tag<variant<Ts...>>.
With Boost.Mp11 this is a short, one-liner:
template<typename Variant, typename T>
constexpr size_t IndexInVariant = mp_find<Variant, T>::value;
Full example:
#include <variant>
#include <boost/mp11/algorithm.hpp>
using namespace boost::mp11;
template<typename Variant, typename T>
constexpr size_t IndexInVariant = mp_find<Variant, T>::value;
int main()
{
using V = std::variant<int,double, char, double>;
static_assert(IndexInVariant<V, int> == 0);
// for duplicates first idx is returned
static_assert(IndexInVariant<V, double> == 1);
static_assert(IndexInVariant<V, char> == 2);
// not found returns ".end()"/ or size of variant
static_assert(IndexInVariant<V, float> == 4);
// beware that const and volatile and ref are not stripped
static_assert(IndexInVariant<V, int&> == 4);
static_assert(IndexInVariant<V, const int> == 4);
static_assert(IndexInVariant<V, volatile int> == 4);
}
One fun way to do this is to take your variant<Ts...> and turn it into a custom class hierarchy that all implement a particular static member function with a different result that you can query.
In other words, given variant<A, B, C>, create a hierarchy that looks like:
struct base_A {
static integral_constant<int, 0> get(tag<A>);
};
struct base_B {
static integral_constant<int, 1> get(tag<B>);
};
struct base_C {
static integral_constant<int, 2> get(tag<C>);
};
struct getter : base_A, base_B, base_C {
using base_A::get, base_B::get, base_C::get;
};
And then, decltype(getter::get(tag<T>())) is the index (or doesn't compile). Hopefully that makes sense.
In real code, the above becomes:
template <typename T> struct tag { };
template <std::size_t I, typename T>
struct base {
static std::integral_constant<size_t, I> get(tag<T>);
};
template <typename S, typename... Ts>
struct getter_impl;
template <std::size_t... Is, typename... Ts>
struct getter_impl<std::index_sequence<Is...>, Ts...>
: base<Is, Ts>...
{
using base<Is, Ts>::get...;
};
template <typename... Ts>
struct getter : getter_impl<std::index_sequence_for<Ts...>, Ts...>
{ };
And once you establish a getter, actually using it is much more straightforward:
template <typename T, typename V>
struct get_index;
template <typename T, typename... Ts>
struct get_index<T, std::variant<Ts...>>
: decltype(getter<Ts...>::get(tag<T>()))
{ };
That only works in the case where the types are distinct. If you need it to work with independent types, then the best you can do is probably a linear search?
template <typename T, typename>
struct get_index;
template <size_t I, typename... Ts>
struct get_index_impl
{ };
template <size_t I, typename T, typename... Ts>
struct get_index_impl<I, T, T, Ts...>
: std::integral_constant<size_t, I>
{ };
template <size_t I, typename T, typename U, typename... Ts>
struct get_index_impl<I, T, U, Ts...>
: get_index_impl<I+1, T, Ts...>
{ };
template <typename T, typename... Ts>
struct get_index<T, std::variant<Ts...>>
: get_index_impl<0, T, Ts...>
{ };
My two cents solutions:
template <typename T, typename... Ts>
constexpr std::size_t variant_index_impl(std::variant<Ts...>**)
{
std::size_t i = 0; ((!std::is_same_v<T, Ts> && ++i) && ...); return i;
}
template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T>(static_cast<V**>(nullptr));
template <typename T, typename V, std::size_t... Is>
constexpr std::size_t variant_index_impl(std::index_sequence<Is...>)
{
return ((std::is_same_v<T, std::variant_alternative_t<Is, V>> * Is) + ...);
}
template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T, V>(std::make_index_sequence<std::variant_size_v<V>>{});
If you wish a hard error on lookups of not containing type or duplicate type - here are static asserts:
constexpr auto occurrences = (std::is_same_v<T, Ts> + ...);
static_assert(occurrences != 0, "The variant cannot have the type");
static_assert(occurrences <= 1, "The variant has duplicates of the type");
Another take on it:
#include <type_traits>
namespace detail {
struct count_index {
std::size_t value = 0;
bool found = false;
template <typename T, typename U>
constexpr count_index operator+(const std::is_same<T, U> &rhs)
{
if (found)
return *this;
return { value + !rhs, rhs};
}
};
}
template <typename Seq, typename T>
struct index_of;
template <template <typename...> typename Seq, typename... Ts, typename T>
struct index_of<Seq<Ts...>, T>: std::integral_constant<std::size_t, (detail::count_index{} + ... + std::is_same<T, Ts>{}).value> {
static_assert(index_of::value < sizeof...(Ts), "Sequence doesn't contain the type");
};
And then:
#include <variant>
struct A{};
struct B{};
struct C{};
using V = std::variant<A, B, C>;
static_assert(index_of<V, B>::value == 1);
Or:
static_assert(index_of<std::tuple<int, float, bool>, float>::value == 1);
See on godbolt: https://godbolt.org/z/7ob6veWGr

Use sizeof with incomplete type inside std::conditional

Here is a minimal example:
struct incomplete_type;
template<typename T>
struct foo
{
using type = std::conditional_t<std::is_arithmetic_v<T>,
std::conditional_t<sizeof(T) < sizeof(void*), int, float>,
double>;
};
foo<incomplete_type> f; will cause error because it will do sizeof with type, even though incomplete_type is not a arithmetic type(iow, it will not go into sizeof branch logically). live demo
So, I want to defer sizeof:
first attempt(fail)
template<typename T>
auto
foo_aux()
{
if(sizeof(T) < sizeof(T*))
return 0;
else
return 0.0f;
}
conditional_t<std::is_arithmetic_v<T>, decltype(foo_aux<T>()), double> still trigger the same error.
second attempt(fail)
template<typename T, bool>
struct foo_aux_aux
{
using type = float;
};
template<typename T>
struct foo_aux_aux<T, true>
{
using type = int;
};
template<typename T, bool = false>
struct foo_aux : foo_aux_aux<T, sizeof(T) < sizeof(void*)>
{};
conditional_t<std::is_arithmetic_v<T>, typename foo_aux<T>::type, double> still trigger the same error.
third attempt(success)
template<typename T, bool comp>
struct foo_aux_aux
{
using type = float;
};
template<typename T>
struct foo_aux_aux<T, true>
{
using type = int;
};
template<typename T, bool isArithmeticType>
struct foo_aux
{
using type = double;
};
template<typename T>
struct foo_aux<T, true>
{
using type = typename foo_aux_aux<T, sizeof(T) < sizeof(void*)>::type;
};
Yes, it works as expected, but its really tedious and ugly.
Do you have an elegant way here?
In C++17, you can use if constexpr to do type computation. Just wrap the type into a dummy container and use value computation, then retrieve the type via decltype.
struct foo could be implemented like:
template<class T>
struct type_ {
using type = T;
};
template<class T>
struct foo {
auto constexpr static type_impl() {
if constexpr (std::is_arithmetic<T>{}) {
if constexpr (sizeof(T) < sizeof(void*)) {
return type_<int>{};
} else {
return type_<float>{};
}
} else {
return type_<double>{};
}
}
using type = typename decltype(type_impl())::type;
};
static_assert(std::is_same<foo<incomplete_type>::type, double>{});
Your second attempt works if you wrap double in type_identity (which is a standard utility in C++20) and move ::type after std::conditional_t<...>:
template<typename T, bool>
struct foo_aux_aux
{
using type = float;
};
template<typename T>
struct foo_aux_aux<T, true>
{
using type = int;
};
template<typename T, bool = false>
struct foo_aux : foo_aux_aux<T, sizeof(T) < sizeof(void*)>
{};
template<typename T>
struct type_identity { using type = T; };
typename std::conditional_t<std::is_arithmetic_v<T>, foo_aux<T>, type_identity<double>>::type
Not a great improvement, I suppose, but you can rewrite your third (working) attempt in a little less ugly (IMHO) way using decltype() and declaring (only) some functions.
I mean something as
struct incomplete_type;
constexpr float baz (std::false_type);
constexpr int baz (std::true_type);
template <typename>
constexpr double bar (std::false_type);
template <typename T>
constexpr auto bar (std::true_type)
-> decltype(baz<std::bool_constant<(sizeof(T) < sizeof(void*))>{});
template<typename T>
struct foo
{ using type = decltype( bar<T>(std::is_arithmetic<T>{}) ); };
You can also use SFINAE:
template <class T1, class T2, class = int (*)[sizeof(T1) < sizeof(T2)]>
constexpr bool DeferSizeof(int) {
return true;
}
template <class, class>
constexpr bool DeferSizeof(...) {
return false;
}
template<typename T>
struct foo
{
using type = std::conditional_t<std::is_arithmetic_v<T>,
std::conditional_t<DeferSizeof<T, void *>(0), int, float>,
double>;
};
With detection idiom from TS Library Fundamentals v2:
template <typename T>
using size_of = std::integral_constant<std::size_t, sizeof(T)>;
template <typename T>
struct foo
{
using type = std::conditional_t<
std::is_arithmetic_v<T>,
std::conditional_t<
std::experimental::detected_or_t<
std::integral_constant<std::size_t, 0>, size_of, T
>{} < sizeof(void*),
int, float>,
double>;
};
DEMO

Expand a type N times in template parameter

I have the following problem:
template< std::size_t N >
class A
{
std::function< std::size_t( /*std::size_t,....,std::size_t <- N-times*/) > foo;
};
As you can see above, I try to declare an std::function<...> foo as a member of a class A. Here, I want foo to have the return type std::size_t (which is no problem) and as input, I will pass N-times the type std::size_t but I don't know how. Is there any possibility?
Many thanks in advance.
You can use std::index_sequence:
template<std::size_t N, typename = std::make_index_sequence<N>>
struct A;
template<std::size_t N, std::size_t... S>
struct A<N, std::index_sequence<S...>> {
std::function<std::size_t(decltype(S)...)> foo;
};
Live example
If you like, you could also define to what type it expands:
template<typename T, std::size_t N, typename = std::make_index_sequence<N>>
struct A;
template<typename T, std::size_t N, std::size_t... S>
struct A<T, N, std::index_sequence<S...>> {
template<std::size_t>
using type = T;
std::function<std::size_t(type<S>...)> foo;
};
For arbitrary type and not just size_t, just write a helper alias:
template<class T, size_t>
using Type = T;
template<std::size_t... S>
struct AHelper<std::index_sequence<S...>> {
std::function<size_t(Type<MyArbitraryTypeHere, S>...)> foo;
};
Ok this was fun. Here is my solution:
namespace details {
template <size_t N, class F = size_t()>
struct Function_type_helper {};
template <size_t N, class... Args>
struct Function_type_helper<N, size_t(Args...)> {
using Type = typename Function_type_helper<N - 1, size_t(Args..., size_t)>::Type;
};
template <class... Args>
struct Function_type_helper<0, size_t(Args...)> {
using Type = size_t(Args...);
};
template <size_t N, class F = size_t()>
using Function_type_helper_t = typename Function_type_helper<N, F>::Type;
static_assert(std::is_same_v<Function_type_helper_t<3>, size_t(size_t, size_t, size_t)>);
} // ns details
template<size_t N>
struct A
{
std::function<details::Function_type_helper_t<N>> foo;
};
This works by recursively creating the type size_t(size_t, size_t, ..., size_t)
For instance:
H<3>::Type == H<3, size_t()>::Type ==
H<2, size_t(size_t)>::Type ==
H<1, size_t(size_t, size_t)>::Type ==
H<0, size_t(size_t, size_t, size_t)>::Type ==
size_t(size_t, size_t, size_t)

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?
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".
Definitely. Here's a more flexible way of doing it, you can add as many ranges as you wish as long as they don't overlap.
template <std::size_t N, class = void>
struct TypeForSize_;
template <std::size_t N>
struct TypeForSize_<N, std::enable_if_t<
(N <= 255)
>> { using type = std::uint8_t; };
template <std::size_t N>
struct TypeForSize_<N, std::enable_if_t<
(N > 255 && N <= 65535)
>> { using type = std::uint16_t; };
template <std::size_t N>
using TypeForSize = typename TypeForSize_<N>::type;
Using a size for which no type has been defined will result in a compile-time error.
Let's just go overkill. Start with a chooser:
template <int I> struct choice : choice<I + 1> { };
template <> struct choice<10> { };
struct otherwise { otherwise(...) { } };
Then create a cascading series of overloads that return a type. The choice ensures that the smallest type will be selected first, without having to write two-sided ranges for all the intermediate integer types:
template <class T> struct tag { using type = T; }
template <size_t N> using size_t_ = std::integral_constant<size_t, N>;
template <size_t N, class = std::enable_if_t<(N < (1ULL << 8))>>
constexpr tag<uint8_t> tag_from_size(size_t_<N>, choice<0> ) { return {}; }
template <size_t N, class = std::enable_if_t<(N < (1ULL << 16))>>
constexpr tag<uint16_t> tag_from_size(size_t_<N>, choice<1> ) { return {};
template <size_t N, class = std::enable_if_t<(N < (1ULL << 32))>>
constexpr tag<uint32_t> tag_from_size(size_t_<N>, choice<2> ) { return {}; }
template <size_t N>
constexpr tag<uint64_t> tag_from_size(size_t_<N>, otherwise) { return {}; }
And then you can write the top level one that dispatches:
template <size_t N>
using type_from_size_t = typename decltype(tag_from_size(size_t_<N>{}, choice<0>{}))::type;
And use it:
template <size_t N>
class X {
type_from_size_t<N> t;
};
First write static_if<bool>( A, B ).
Next, write template<class T> struct tag_type{using type=T;}; and supporting code.
template<size_t n>
constexpr auto type_from_size() {
return static_if< (n<256) >(
tag<std::uint8_t>,
tag<std::uint16_t>
);
}
now returns a different tag type based on the value of n.
To use:
template<size_t n>
class X {
typename decltype(type_from_size<n>())::type t;
}
or write a quick alias:
template<size_t n> type_from_size_t = typename decltype(type_from_size<n>())::type;
Here is the code for tag_type and static_if:
template<class T>struct tag_type{using type=T;};
template<class T>constexpr tag_type<T> tag{};
template<bool b>using bool_t=std::integral_constant<bool, b>;
template<class T, class F>
constexpr T static_if( bool_t<true>, T t, F f ) {
return t;
}
template<class T, class F>
constexpr F static_if( bool_t<false>, T t, F f ) {
return f;
}
template<bool b, class T, class F>
constexpr auto static_if( T t, F f ) {
return static_if( bool_t<b>, t, f );
}
template<bool b, class T>
constexpr auto static_if( T t ) {
return static_if<b>( t, [](auto&&...){} );
}
and done.
Note we can also do static_case. :)
constexpr function cannot return a type directly but a simple wrapper will work.
template <typename T>
struct TypeWrapper {
using type = T;
};
template <size_t n>
constexpr auto type_from_size_func() {
if constexpr (n <= 0xff) {
return TypeWrapper<uint8_t>{};
} else {
if constexpr (n <= 0xffff) {
return TypeWrapper<uint16_t>{};
} else {
return TypeWrapper<uint32_t>{};
}
}
}
template <size_t N>
using type_from_size = typename decltype(type_from_size_func<N>())::type;
usage
type_from_size<123> x; /*uint8_t*/
type_from_size<1234> y; /*uint16_t*/
type_from_size<12345678> z; /*uint32_t*/