我想实现对一组变量中的一个类的静态转换,作为可变参数模板参数传递:
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的实现。在编译时如何迭代候选人...可能是最困难的部分。
答案 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"};
}
}
如果返回类型可以不同,则应使用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);
为获得更好的性能,应使用类似于以下内容的跳转表:
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);
}
答案 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;
}