this is what i want, a "switch" type trait that returns the first type which has a condition == true:
ext::select_t<condition1 == true, Type1,
condition2 == true, type2,
condition3 == true, type3>
etc, and be able to add as many condition / type pairs as i want.
i can do this with std::conditional as such (random example):
template<typename Number,
typename Distribution = std::conditional_t<
// IF
std::is_integral<Number>::value,
// RETURN INT
std::uniform_int_distribution<Number>,
// ELSE
std::conditional_t<std::is_floating_point<Number>::value,
// RETURN REAL
std::uniform_real_distribution<Number>, void>>>
Number random(Number min, Number max)
{
static std::random_device rd;
static std::mt19937 mt(rd());
Distribution dist(min, max);
return dist(mt);
}
as you can see it decides at compile time what kind of distribution i want depending on the conditions/types passed.
obviously this can get real ugly real fast if i try to add more conditions, imagine i want 10 of them.
so i tried to build one, but failed miserably:
template<bool B, typename T>
struct cond
{
static constexpr bool value = B;
using type = T;
};
template<typename Head, typename... Tail>
struct select
{
using type = std::conditional_t<Head::value, typename Head::type, select<Tail...>>;
};
template<typename Head>
struct select<Head>
{
using type = std::conditional_t<Head::value, typename Head::type, void>;
};
template<typename Head, typename... Tail>
using select_t = typename select<Head, Tail...>::type;
the reason i tried to make a cond structure is so i can get "pairs" of conditions/types, so i can get any number of those using variadic templates, but this makes it even more uglier (and not working):
using Type = std::select_t<cond<false, void>,
cond<false, int>,
cond<true, std::string>>;
not only it doesn't look as good as i want the final version to be, but it doesn't even work! it only works when the first condition is true..
is there anything im missing? also how could i achieve this in a more clean way (at least for the end user).
thanks in advance.
The problem is in your base case:
using type = std::conditional_t<Head::value, typename Head::type, select<Tail...>>;
You want on success (Head::value
) to use the head type (Head::type
), but on failure to use the tail type. But select<Tail...>
isn't the tail type. That's a metafunction. You want to actually evaluate it:
using type = std::conditional_t<
Head::value,
typename Head::type,
typename select<Tail...>::type>;
Now this is a little inefficient, since you have to process the entirety of the conditional up top. For that, you could write a separate metafunction which Boost.MPL had called eval_if
. Instead of taking a boolean and two types, it takes a boolean and two metafunctions:
template <bool B, typename TrueF, typename FalseF>
struct eval_if {
using type = typename TrueF::type;
};
template <typename TrueF, typename FalseF>
struct eval_if<false, TrueF, FalseF> {
using type = typename FalseF::type;
};
template <bool B, typename T, typename F>
using eval_if_t = typename eval_if<B, T, F>::type;
With which your main case for select
becomes:
template<typename Head, typename... Tail>
struct select
{
using type = eval_if_t<Head::value,
Head,
select<Tail...>>;
};
Although on reflection, the same could be accomplished with std::conditional_t
and inheritance:
template <typename Head, typename... Tail>
struct select
: std::conditional_t<Head::value, Head, select<Tail...>>
{ };
Also, typically we would just have an "else" case at the end, so maybe you'd write your selector as:
using T = select_t<cond<C1, int>,
cond<C2, float>,
double>;
So I would suggest writing your base case thusly:
template <typename T>
struct select<T>
{
using type = T;
};
template <bool B, typename T>
struct select<cond<B, T>>
{
// last one had better be true!
static_assert(B, "!");
using type = T;
};
Also, you wrote std::select_t
... don't put this in namespace std
, put it in your own namespace.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With