Reputation: 627
I want to use std::any in my program but I find myself writing a lot of conditional statements like this:
if (anything.type() == typeid(short)) {
auto s = std::any_cast<short>(anything);
} else if (anything.type() == typeid(int)) {
auto i = std::any_cast<int>(anything);
} else if (anything.type() == typeid(long)) {
auto l = std::any_cast<long>(anything);
} else if (anything.type() == typeid(double)) {
auto d = std::any_cast<double>(anything);
} else if (anything.type() == typeid(bool)) {
auto b = std::any_cast<bool>(anything);
}
Note that I omitted much of the else if conditions for brevity.
My program can use any of the defined types that can be stored in std::any so these if-then statements are quite long. Is there a way to refactor the code so that I can write it once?
My original inclination was to use templates like so:
template<typename T>
T AnyCastFunction(std::any) {
T type;
if (anything.type() == typeid(short)) {
type = std::any_cast<short>(anything);
} else if (anything.type() == typeid(int)) {
type = std::any_cast<int>(anything);
} else if (anything.type() == typeid(long)) {
type = std::any_cast<long>(anything);
} else if (anything.type() == typeid(double)) {
type = std::any_cast<double>(anything);
} else if (anything.type() == typeid(bool)) {
type = std::any_cast<bool>(anything);
}
return type;
}
However, this leads to "couldn't deduce template parameter T" errors. How can I refactor this to avoid writing the large if/else blocks many times throughout the program?
Upvotes: 0
Views: 830
Reputation: 154005
The basic idea is to create an std::any
visitor and do the necessary processing in a function called from the visitor. That basic principle is straight forward. Let's start with supporting just one type:
#include <any>
#include <iostream>
#include <type_traits>
template <typename T, typename Any, typename Visitor>
auto any_visit1(Any&& any, Visitor visit)
-> std::enable_if_t<std::is_same_v<std::any, std::decay_t<Any>>>
{
if (any.type() == typeid(T)) {
visit(std::any_cast<T>(std::forward<Any>(any)));
}
}
int main() {
std::any a0(17);
any_visit1<int>(a0, [](auto value){ std::cout << "value=" << value << "\n"; });
}
The next step is to remove the one type restriction. As the explicit template parameters come first and are an open-ended list and the function object should be a deduced template parameter, you can't quite use a function template. However, a variable template (with an inline constexpr
, of course, hence variable...) does the trick:
#include <any>
#include <iostream>
#include <type_traits>
template <typename... T>
inline constexpr auto any_visit =
[](auto&& any, auto visit) -> std::enable_if_t<std::is_same_v<std::any, std::decay_t<decltype(any)>>> {
(
(any.type() == typeid(T) && (visit(std::any_cast<T>(std::forward<decltype(any)>(any))), true))
|| ...)
// Uncomment the line below to have visit(any) called for unhandled types
// || (visit(std::forward<decltype(any)>(any)), true)
;
};
void test(std::any any)
{
any_visit<int, double, char const*>(any, [](auto value){ std::cout << "value=" << value << "\n"; });
}
int main() {
test(17);
test(3.14);
test(+"foo");
}
If you need multiple std::any
objects decoded you'd just pass suitable [lambda?] functions into it which refer to the other objects and keep building up the object until you got all the ones you need.
Upvotes: 1
Reputation: 275820
I find this type of code fun to write.
any_visitor<types...>
is a function object that visits a set of types.
You invoke it with an any followed by a function object. It then invokes the function object with whichever of the types...
is in the any
.
So you do any_vistor<int, double>{}( something, [](auto&& x) { /* some code */ } )
.
If none of the types...
are in the any
, it invokes the function object with a std::any
for you to deal with the extra case.
We can also write a variant that instead of passing the std::any
to the functor, throws or returns false or something.
template<class...Ts>
struct any_visitor;
template<>
struct any_visitor<> {
template<class F>
decltype(auto) operator()( std::any& a, F&& f ) const {
return std::forward<F>(f)(a);
}
};
template<class...Ts>
struct any_visitor {
private:
struct accum {
std::size_t x = 0;
friend accum operator+( accum lhs, accum rhs ) {
if (lhs.x || rhs.x) return {lhs.x+1};
else return {};
}
};
public:
template<class Any, class F>
void operator()(Any&& any, F&& f) const {
// sizeof...(Ts) none in the list
// otherwise, index of which any is in the list
std::size_t which = sizeof...(Ts) - (accum{} + ... + accum{ any.type() == typeid(Ts) }).x;
using table_entry = void(*)(Any&&, F&&);
static const table_entry table[] = {
+[](Any&& any, F&& f) {
std::forward<F>(f)( std::any_cast<Ts>( std::forward<Any>(any) ) );
}...,
+[](Any&& any, F&& f) {
std::forward<F>(f)( std::forward<Any>(any) );
}
};
table[which]( std::forward<Any>(any), std::forward<F>(f) );
}
};
template<class...Fs>
struct overloaded:Fs... {
using Fs::operator()...;
};
template<class...Fs>
overloaded(Fs&&...)->overloaded<std::decay_t<Fs>...>;
I also included overloaded
which makes it easier to dispatch. If you want to handle all types uniformly, except handle an error case, you can do:
overloaded{
[](auto const& x){ std::cout << x << "\n"; },
[](std::any const&){ std::cout << "Unknown type\n"; }
}
and pass that as the function object to any_visitor
.
Here is some test code:
std::any foo=7;
std::any bar=3.14;
auto visitor = overloaded{
[](int x){std::cout << x << "\n";},
[](auto&&){std::cout << "Unknown\n";}
};
any_visitor<int>{}( foo, visitor );
any_visitor<int>{}( bar, visitor );
which outputs:
7 Unknown
Implementation wise, this code uses a dispatch table (sort of like a vtable) to map the index of the type stored in the any to which overload of the function object we invoke.
Yet another approach would be to write:
template<class...Ts>
std::optional<std::variant<Ts...>> to_variant( std::any );
which converts a std::any
to a variant if its types match. Then use the usual visiting machinery on std::variant
instead of rolling your own.
Upvotes: 1
Reputation: 303537
If you have a known, fixed list of possible types, don't use std::any
. Use std::variant<Ts...>
. That makes Dietmar's answer look like this:
#include <variant>
void test(std::variant<int, double, char const*> v)
{
std::visit([](auto value){ std::cout << "value=" << value << "\n"; }, v);
}
which is the same thing, except (a) you don't have to implement visit
yourself (b) this is massively more efficient at runtime and (c) this is type safe - you can't forget to check a particular type! Really even if you don't care about (a) or (b), (c) is a huge win.
And if you don't have a known, fixed list of possible types - which is the typical use-case for wanting std::any
- then anything you're doing with std::any
doesn't make sense anyway. You can't enumerate all possible copyable types (there are an infinite amount of them), so you can't necessarily retrieve the contents. So I really think variant
is what you want.
Upvotes: 3
Reputation: 11940
Well, if you're sure you need such a broad range stored in any
...
template<typename T> void visit(T &&t) { std::cout << "Hi " << t << "!\n"; }
void try_visit(std::any &&) { std::cout << "Unknown type\n"; }
template<typename T, typename... Ts> void try_visit(std::any thing) {
if(thing.type() == typeid(T)) {
visit(std::any_cast<T>(thing));
return;
}
if constexpr(sizeof...(Ts) > 0) try_visit<Ts...>(std::move(thing));
else try_visit(std::move(thing));
}
int main() {
try_visit<short, int, double, bool, long>(std::any{42});
}
%-}
Upvotes: 1