can you switch over a std::any.type()?

Viewed 725

I want to explore how I can use std::any instead of void * or such constructs for message passing. So I created an example code to test this - see below.

The use of std::any looks nice, but I want to switch through the types to check which type the std::any is. It might no be possible, and I know I can use a if/elseif... block instead, but it would be very nice if I can create a switch statement so that if I use this in real code with 10-20 different types it will be more readable.

#include <string>  
#include <iostream>  
#include <sstream>  
#include <any>  
#include <typeindex>  

struct ints { int a{1}; int b{2}; };
struct strings { std::string a{"string1"}; std::string b{"string2"}; };

void send_msg(std::any item)
{
    switch (item.type().hash_code())       // <------- HERE
    {
        case typeid(ints).hash_code():     // <------- HERE
            std::cout << "ints" << std::endl;
            break;
        case typeid(strings).hash_code():
            std::cout << "strings" << std::endl;
            break;
        default:
            std::cout << "unknown type\n";
    }
}

int main()
{
    strings s;
    send_msg(s);

    ints i;
    send_msg(i);
}

live example: https://godbolt.org/z/xPrMYM

I can't switch on the type_info returned by std::any::type, but I can switch on the hash_code() of the type_info, but that is not a constexpr I hoped it was though!

so I also tried getting the address of the type info and a few other tricks that I could find. But no luck so far...

Is there such a way?

2 Answers

I would also suggest a variant here if possible but for the concrete question:

If you can use boost and C++17 is your referred standard: They provide an extended typeid handling that can also be used for several compile time checks, see

https://www.boost.org/doc/libs/1_67_0/boost/type_index/ctti_type_index.hpp

Its underlying rawType is a const char* constexpr and the typeIndex itself can be used as a perfect replacement for std::type_info. Since the standard guarantees unique addresses for these type identifiers, even a simple address comparison should be possible here (not sure why this simplification is currently out commented inside the boost headers though).

To be usable with any, you might have to wrap it or to use a simple own any type.

What you're asking is std::visit, but with std::any

template<typename... Ts, typename F>
bool visit(F&& f, std::any x)
{
    auto result = ((x.type() == typeid(Ts) ?
        (std::forward<F>(f)(*std::any_cast<Ts>(&x)), true) : false) || ...);
    return result;
}

Since the std::any might contain anything, the visit might fail at runtime, which is why you certainly need some way to report the error†.

Use as

bool send_msg(std::any x)
{
    auto f = [](auto&& x){
        std::cout << x << '\n';
    };
    return visit<std::string, int>(f, x);
}

But then, using std::variant is just simpler

void send_msg(std::variant<std::string, int> x)
{
    auto f = [](auto&& x){
        std::cout << x << '\n';
    };
    visit(f, x);
}

†The complete visit implementation would be a bit more complicated.

Related