Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static cast to a class from a limited set of classes

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.

like image 327
cos Avatar asked Dec 04 '18 17:12

cos


2 Answers

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

like image 145
Piotr Skotnicki Avatar answered Oct 13 '22 18:10

Piotr Skotnicki


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;
 }
like image 34
max66 Avatar answered Oct 13 '22 16:10

max66