Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: check if the value stored in std::any is integral

Tags:

c++

c++20

stdany

There's a value stored in std::any and I want to know if it's an integral value (char, short, int, long, both signed and unsigned) or a floating point value (float, double), or something else.

If I only had to check for int values, I would do this:

std::any a;
if (a.type() == typeid(int)) {
  // do some logic for int
}

But to do a giant if (a.type() == typeid(int) || a.type() == typeid(signed char)...) for all the integral types in C++ seems... bad.

I could use is_arithmetic<T> from type_traits if I had the type T accessible somehow but I don't, I only have std::any#type() which returns std::type_info.

Is there a way to accomplish this without many, many disjunctions in the if condition?

(I'm using C++20 if it's important)

like image 204
wouldnotliketo Avatar asked Apr 25 '21 12:04

wouldnotliketo


1 Answers

std::any is a type erasure class.

It only remembers the exact type, and the ability to cast back to that exact type (plus how to copy/move/destroy it).

If you want to remember other facts about a type stored, you have to do the work yourself.

There is no way to get at the T within std::any without checking for that exact T.

In general, using std::any to shove "anything" into it with no control results in a mess. std::any allows you to do this with an "open" set of types, so you can safely pass data from one spot of code to another while the intermediate code doesn't need to know what it is.

It does not give you the ability to generate type-aware code using an unknown type.


To solve your problem, there are a number of solutions.

  1. If your set of types you support is closed (fixed somehow), use std::variant.

  2. If the set of types is mostly closed, use std::variant< bunch, of, types, std::any >. Then you can deal with the "mostly closed" types as a variant. Using code that blocks conversion to std::any if the type input can be converted-to any other type might be wise.

  3. If you are comfortable writing your own type erasure, you can write your own or augment std::any with extra information.

  4. You can write a utility function that does the massive if statement, possibly using templates, in time linear in the number of types.

For 1/2,

auto is_integral_f = [](auto&& x){ return std::is_integral<std::decay_t<decltype(x)>>{}; };

std::variant<int,char,unsigned int, long, double, std::any> bob;
bob = 3;
assert( std::visit( is_integral_f, bob ) );

For 3, here is an example of an engine that makes type erasure this way a bit simpler; writing it yourself is possible. Then we simply:

auto is_integral = any_method<bool()>{ is_integral_f };

super_any<decltype(is_integral)> my_any;

my_any bob = 3;
my_any alice = 3.14;
assert( (bob->*is_integral)() );
assert( !(alice->*is_integral)() );

For 4,

template<class...Ts>
bool is_any_of_types( std::any const& a ) {
  return (( a.type() == typeid(Ts) ) || ... );
}

which is linear in sizeof...(Ts).

You could get fancy with hashes if Ts... is large, but I doubt it will get large enough. You still need to enumerate the types yourself; is_integral<T> cannot be inverted by the C++ language.

like image 76
Yakk - Adam Nevraumont Avatar answered Oct 19 '22 12:10

Yakk - Adam Nevraumont