This is a similar question to Using std::forward on sub fields, but the answer there doesn't seem to apply in my case.
template<class Base, class F>
void visit(Base&&, const F&) {
throw std::bad_cast();
}
template<class Derived, class... Rest, class Base, class F>
void visit(Base&& base, const F& f) {
if (auto *as_derived = dynamic_cast<Derived *>(&base)) {
return f(std::forward<Base>(*as_derived));
} else {
return visit<Rest...>(std::forward<Base>(base), f);
}
}
My goal is that the following test case work:
struct Animal {
virtual ~Animal() {}
};
struct Cat : Animal {
void speak() & { puts("meow"); }
void yowl() && { puts("MEOW!"); }
};
struct Dog : Animal {
void speak() & { puts("woof"); }
void yowl() && { puts("WOOF!"); }
};
int main() {
Animal *a = new Cat();
Animal *b = new Dog();
visit<Cat, Dog>(*a, [](auto&& a){ std::forward<decltype(a)>(a).speak(); });
visit<Cat, Dog>(*b, [](auto&& a){ std::forward<decltype(a)>(a).speak(); });
visit<Cat, Dog>(std::move(*a), [](auto&& a){ std::forward<decltype(a)>(a).yowl(); });
visit<Cat, Dog>(std::move(*b), [](auto&& a){ std::forward<decltype(a)>(a).yowl(); });
}
Desired output: "meow" "woof" "MEOW!" "WOOF!". Notice that the functions speak
and yowl
are non-virtual; this is required in my original code because they are actually templates, and templates can't be virtual.
The problem with this code as written here is that std::forward<Base>(*as_derived)
doesn't merely change the ref-qualifiers and const-qualifiers on *as_derived
to enable perfect forwarding; it actually casts the type back up to Base&
, nerfing the whole point of visit
!
Is there a standard library function that does what I want std::forward
to do — namely, change the ref-qualifiers and const-qualifiers on *as_derived
to match the ones that would be perfect-forwardly deduced from std::forward<Base>
?
If there's no standard library function, how could I write a "perfect forward a child type" function for my own use?
The Wandbox link above contains something that "works" for this test case, but it doesn't preserve constness and it doesn't look elegant at all.
There isn't anything in the standard for this. But it isn't hard to write. Just annoying. What you need to do is write a trait that gives you the type to pass into forward
- basically you want to match the cv-qualifications and reference-ness of Derived
to what Base
is, and then pass that type into forward
:
return f(std::forward<match_ref_t<Base, Derived>>(*as_derived));
A simple implementation, that can almost certainly be made more concise, is just:
template <class From, class To>
struct match_ref {
using type = To;
};
template <class From, class To>
using match_ref_t = typename match_ref<From, To>::type;
template <class From, class To>
struct match_ref<From&, To> {
using type = match_ref_t<From, To>&;
};
template <class From, class To>
struct match_ref<From&&, To> {
using type = match_ref_t<From, To>&&;
};
template <class From, class To>
struct match_ref<From const, To> {
using type = match_ref_t<From, To> const;
};
template <class From, class To>
struct match_ref<From volatile, To> {
using type = match_ref_t<From, To> volatile;
};
template <class From, class To>
struct match_ref<From const volatile, To> {
using type = match_ref_t<From, To> const volatile;
};
Or, I guess:
template <class Check, template <class> class F, class T>
using maybe_apply = std::conditional_t<Check::value, F<T>, T>;
template <class From, class To>
struct match_ref {
using non_ref = std::remove_reference_t<From>;
using to_cv = maybe_apply<std::is_const<non_ref>, std::add_const_t,
maybe_apply<std::is_volatile<non_ref>, std::add_volatile_t,
To>>;
using type = std::conditional_t<
std::is_lvalue_reference<From>::value,
to_cv&,
std::conditional_t<
std::is_rvalue_reference<From>::value,
to_cv&&,
to_cv>
>;
};
template <class From, class To>
using match_ref_t = typename match_ref<From, To>::type;