I want to implement a static cast to one of the classes from a set, passed as variadic template parameters:
struct Base {
int tag_value;
};
struct Derived1 : public Base {
static constexpr int tag = 1;
Derived1() : Base{tag} {}
int foo() { return 100; }
};
struct Derived2 : public Base {
static constexpr int tag = 2;
Derived2() : Base{tag} {}
int foo() { return 200; }
};
struct Derived3 : public Base {
static constexpr int tag = 3;
Derived3() : Base{tag} {}
int foo() { return 300; }
};
template <class ... Candidates, class Fn>
auto apply_casted(Base & base, Fn fn) {
//compare base::tag_value with each Candidate::tag
//static_cast<> base to Candidate if match
//call fn with base casted to matched Derived
return fn(/*...*/);
}
int main() {
Derived2 d2;
Base & b = d2;
// should throw error (b.tag_value doesn't match neither Derived1::tag nor Derived3::tag
auto v1 = apply_casted<Derived1, Derived3>(b, [](auto d) {
return d.foo();
});
// should static_cast b to Derived2 and return foo() (200)
auto v2 = apply_casted<Derived1, Derived2>(b, [](auto d) {
return d.foo(); //calls Derived2::foo()
});
}
Well, I hope the code speaks for itself. Code to get started: https://godbolt.org/z/WfaFt- I'm looking for implementation of apply_casted. How to iterate Candidates... at compile time is probably the most difficult part.
template <typename Candidate, typename... Candidates, typename Fn>
auto apply_casted(Base& base, Fn&& fn)
{
if (base.tag_value == Candidate::tag)
{
return std::forward<Fn>(fn)(static_cast<Candidate&>(base));
}
if constexpr (sizeof...(Candidates) > 0)
{
return apply_casted<Candidates...>(base, std::forward<Fn>(fn));
}
else
{
throw std::runtime_error{"tag_value doesn't match"};
}
}
DEMO
If the return types can differ, a common one should be specified as a result of apply_casted
:
std::common_type_t<std::invoke_result_t<Fn, Candidate&>
, std::invoke_result_t<Fn, Candidates&>...>
A similar functionality can be achieved with std::variant
:
template <typename... Ts> struct overload : Ts... { using Ts::operator()...; };
template <typename... Ts> overload(Ts...) -> overload<Ts...>;
std::variant<Derived1, Derived2, Derived3> v;
v.emplace<Derived2>();
std::visit(overload{ [](Derived2& d) -> int { return d.foo(); },
[](auto& d) -> int { throw std::runtime_error{""}; } }, v);
DEMO 2
For a better performance, you should use a jump table, similar to the below one:
template <typename R, typename F, typename V, typename C>
struct invoker
{
static R invoke(F&& f, V&& v)
{
return f(static_cast<C&&>(v));
}
};
template <typename Candidate, typename... Candidates, typename Fn>
auto apply_casted(Base& base, Fn&& fn)
{
using R = std::common_type_t<std::invoke_result_t<Fn, Candidate&>
, std::invoke_result_t<Fn, Candidates&>...>;
using invoker_t = R(*)(Fn&&, Base&);
invoker_t arr[]{ &invoker<R, Fn, Base&, Candidate&>::invoke
, &invoker<R, Fn, Base&, Candidates&>::invoke... };
return arr[base.tag_value](std::forward<Fn>(fn), base);
}
DEMO 3
It's too late too play?
You tagged C++17, so you can use template folding (modified following a Frank's suggestion (thanks!))
template <class ... Candidates, class Fn>
auto apply_casted(Base & base, Fn fn)
{
int ret {-1};
if ( false == ((Candidates::tag == base.tag_value
? ret = fn(static_cast<Candidates&>(base)), true
: false) || ...) )
; // throw something
return ret;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With