Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

static_assert that a type is among a std::variant's accepted types

In C++17 how can one verify in a constexpr that a type belongs to the typelist of a variant ?

e.g:

using MyVt = std::variant<int, float>;
static_assert( MyVt::has_type< bool >::value, "oops, forgot bool");

or

static_assert( mpl::has_key< MyVt::typelist, T >::value, "oops, forgot T");

Of course more useful in concept expressions, or just as static_assert in a template function; to restrict the possible types accepted.

If we don't have access to an explicitly supported standard metafunction or metalist for this, would it be possible to hack a check using SFINAE involving a constructor expression ?

like image 1000
v.oddou Avatar asked Aug 26 '26 00:08

v.oddou


2 Answers

The basic solution uses a fold expression (C++17) and partial specialization:

#include <type_traits>
#include <variant>

template<class T, class TypeList>
struct IsContainedIn;

template<class T, class... Ts>
struct IsContainedIn<T, std::variant<Ts...>>
  : std::bool_constant<(... || std::is_same<T, Ts>{})>
{};

using MyVt = std::variant<int, float>;
static_assert(IsContainedIn<bool, MyVt>::value, "oops, forgot bool");

You can make it more generic by using a template template parameter. This way, it also works for std::tuple, std::pair, and other templates. Those other templates must use only type template parameters, though (e.g., std::array does not match the template template parameter template<class...> class Tmpl in the example below).

template<class T, template<class...> class Tmpl, class... Ts>
struct IsContainedIn<T, Tmpl<Ts...>>
  : std::bool_constant<(... || std::is_same<T, Ts>{})>
{};

Finally, this good C++17 answer to a C++11 question uses std::disjunction instead of a fold expression. You can think of std::disjunction as the functional any_of. This enables short-circuit evaluation (at compile time). In this case it reads

template<class T, template<class...> class Tmpl, class... Ts>
struct IsContainedIn<T, Tmpl<Ts...>>
  : std::disjunction<std::is_same<T, Ts>...>
{};

The cppreference notes on std::disjunction state that

[...]

The short-circuit instantiation differentiates disjunction from fold expressions: a fold expression like (... || Bs::value) instantiates every B in Bs, while std::disjunction_v<Bs...> stops instantiation once the value can be determined. This is particularly useful if the later type is expensive to instantiate or can cause a hard error when instantiated with the wrong type.

like image 117
Julius Avatar answered Aug 27 '26 18:08

Julius


Not a great difference but an alternative to the Julius's answer can the use of the same check (std::bool_constant<(... || std::is_same<T, Ts>{}) or, better, std::disjunction<std::is_same<T, Ts>...>) be the same things through the declaration of a constexpr function and a template constexpr variable

template <typename T, template <typename...> class C, typename ... Ts>
constexpr auto isTypeInList (C<Ts...> const &)
    -> std::disjunction<std::is_same<T, Ts>...>;

template <typename T, typename V>
static constexpr bool isTypeInList_v 
   = decltype(isTypeInList<T>(std::declval<V>()))::value;

and you can use they as follows

using MyVt = std::variant<int, float>;

static_assert( isTypeInList_v<int, MyVt> );
static_assert( isTypeInList_v<double, MyVt> == false );

Not a great improvement but... if you also define (non only declare) the isTypeInList() function

template <typename T, template <typename...> class C, typename ... Ts>
constexpr auto isTypeInList (C<Ts...> const &)
    -> std::disjunction<std::is_same<T, Ts>...>
 { return {}; } 

you can also use it directly to check objects

MyVt myVar {0};

static_assert( isTypeInList<int>(myVar) );

avoiding the need of pass through a decltype()

MyVt myVar {0};

static_assert( isTypeInList_v<int, decltype(myVar)> );
like image 27
max66 Avatar answered Aug 27 '26 16:08

max66



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!