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

2

8

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?

Allard answered 14/12, 2020 at 15:42 Comment(16)
You might be looking for std::variant and std::visitFucoid
@IgorTandetnik maybe, but I am not sure.. in my noddy example you are probably right, but I want to expand on this and turn it into a message passing framework between (for example) threads and such where buffers of data (maybe a vector<std::any>) can be used. My understand of variant is that it is more like a union, whereas any contains the actual type you want and is therefore the correct size (not just the largest?) and also destructs the correct object when reset/reassigned...Allard
and also has type saftey - so you can only pick the correct type other wise you get a bad cast... etc...Allard
@code_fodder: In a message passing framework, the type of message ought to be what determines the type of the data within the message. So you shouldn't be switching over any's type; you switch over the type of the message, and the handler for that message's type knows what type to cast the any to.Drover
Variant also destroys the correct object and has type safety.Pallid
Here is one about doing "typeid" in constexpr: https://mcmap.net/q/16725/-can-i-obtain-c-type-names-in-a-constexpr-way/35943472#35943472Sicilia
@Allard That's right, but any is just an unconstrained variant, and if you're switching over types that suggests you want it to be constrained.Depersonalize
@NicolBolas this is the classical way to do it, but I am experimenting and I would like to see if I can do it this way - as I say, it may not be possible :)Allard
Switching over types is problematic when you have classes that derive from each other. A single object might match multiple types (e.g., a std::any that holds a Derived also holds a Base). If you require strict matching, and somebody does a struct myints : ints { }; and puts ` myints` in the std::any, then you won't recognize it, which violates the Liskov Substitution Principle.Fortune
@Pallid maybe I just don't get the use-case for std::any yet then! - I was reading that it can be used to replace void* and such things - so I guess we can use either std::any or std::variant and the difference is a bit subtle...Allard
@AsteroidsWithWings sorry, I can't reply to two threads at once, but I think my previous comment is relevant to your reply as well :)Allard
@RaymondChen ah, which bring us back to Nicol Bolas's comment about using a message type and then doing the any/variant cast to get the corect data... ok that makes sense :)Allard
@code_fodder: "I was reading that it can be used to replace void and such things*" It can. You can only correctly use void* if you know the correct type to cast it to. You can't "switch over types" with a void*, so what you're trying to do wouldn't work with a void* either.Drover
@code_fodder: More to the point, any is meant to be used for these circumstancesDrover
@NicolBolas I see your point now : ) ... Although I still think my use-case matches your answer about passing data between A and B via C (ok my example is not quite there), but where A and B will know what the data is bsaed on the type field (that I am missing). So if I update my example to have a type field then it all makes more sense and I can swtich over the type enum/int thanks. This is probably as good an answer as I need so feel free to post that upAllard
You could take a look at CTTI which has a typeid which evaluates at compile time. See the examples in the ReadmeFidelfidela
R
1

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.

Roundly answered 15/12, 2020 at 9:31 Comment(0)
S
1

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.

Soche answered 15/12, 2020 at 9:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.