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?

code_fodder
  • 15,263
  • 17
  • 90
  • 167
  • 8
    You might be looking for `std::variant` and `std::visit` – Igor Tandetnik Dec 14 '20 at 15:45
  • @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) 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... – code_fodder Dec 14 '20 at 15:58
  • and also has type saftey - so you can *only* pick the correct type other wise you get a bad cast... etc... – code_fodder Dec 14 '20 at 15:59
  • @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. – Nicol Bolas Dec 14 '20 at 16:02
  • 3
    Variant also destroys the correct object and has type safety. – Quimby Dec 14 '20 at 16:02
  • Here is one about doing "typeid" in constexpr: [https://stackoverflow.com/questions/35941045/can-i-obtain-c-type-names-in-a-constexpr-way/35943472#35943472](https://stackoverflow.com/questions/35941045/can-i-obtain-c-type-names-in-a-constexpr-way/35943472#35943472) – Ranoiaetep Dec 14 '20 at 16:09
  • 2
    @code_fodder 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. – Asteroids With Wings Dec 14 '20 at 16:12
  • @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 :) – code_fodder Dec 14 '20 at 16:33
  • 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. – Raymond Chen Dec 14 '20 at 16:37
  • @Quimby 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... – code_fodder Dec 14 '20 at 16:37
  • @AsteroidsWithWings sorry, I can't reply to two threads at once, but I think my previous comment is relevant to your reply as well :) – code_fodder Dec 14 '20 at 16:39
  • @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 :) – code_fodder Dec 14 '20 at 16:40
  • 1
    @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. – Nicol Bolas Dec 14 '20 at 16:42
  • 2
    @code_fodder: More to the point, `any` is meant to be used for [these circumstances](https://stackoverflow.com/a/51882510/734069) – Nicol Bolas Dec 14 '20 at 16:44
  • @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 up – code_fodder Dec 14 '20 at 16:54
  • You could take a look at [CTTI](https://github.com/Manu343726/ctti) which has a `typeid` which evaluates at compile time. See the examples in the Readme – Mike van Dyke Dec 15 '20 at 09:58

2 Answers2

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.

Secundi
  • 1,150
  • 1
  • 4
  • 12
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.

Passer By
  • 19,325
  • 6
  • 49
  • 96