从一组有限的类中静态转换为一个类

时间:2018-12-04 17:20:34

标签: c++ templates c++17 variadic-templates template-meta-programming

我想实现对一组变量中的一个类的静态转换,作为可变参数模板参数传递:

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()
    });
}

好吧,我希望代码能说明一切。入门代码:https://godbolt.org/z/WfaFt- 我正在寻找apply_casted的实现。在编译时如何迭代候选人...可能是最困难的部分。

2 个答案:

答案 0 :(得分:4)

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


如果返回类型可以不同,则应使用apply_casted作为返回的通用类型:

std::common_type_t<std::invoke_result_t<Fn, Candidate&>
                 , std::invoke_result_t<Fn, Candidates&>...>

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


为获得更好的性能,应使用类似于以下内容的跳转表:

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

答案 1 :(得分:1)

太晚了也不能玩吗?

您标记了C ++ 17,因此可以使用模板折叠(根据弗兰克的建议进行了修改(谢谢!))

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;
 }