6

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)

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
wouldnotliketo
  • 153
  • 1
  • 10
  • [`std::any_cast`](https://en.cppreference.com/w/cpp/utility/any/any_cast) and catch the thrown exception if it's the wrong type? But note that `int` can be converted to just about any other numeric type, so this might not work. With that said, generally speaking, using `typeid` to select program paths is typically a sign of bad design. – Some programmer dude Apr 25 '21 at 12:33
  • That is how std any works and that’s how limited it is. If you want something more fancy use your how type erasure (e.g. built on top of std any) or use another library, like Boost.TypeErasure https://www.boost.org/doc/libs/1_76_0/doc/html/boost_typeerasure.html. The key is that you are looking for a Concept based type erasure rather than type based. – alfC Apr 25 '21 at 18:01

1 Answers1

10

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524