1

This is a similar question to Using std::forward on sub fields, but the answer there doesn't seem to apply in my case.

Consider this code:

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.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159

3 Answers3

4

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;
Barry
  • 286,269
  • 29
  • 621
  • 977
2

Forward is just a conditional move.

template<bool b>
struct move_if_t{
  template<class T>
  T&& operator()(T&t)const{ return std::move(t); }
};
template<>
struct move_if_t<false>{
  template<class T>
  T& operator()(T&t)const{ return t; }
};
template<bool b, class T>
decltype(auto) move_if(T& t){
  return move_if_t<b>{}(t);
}

Now we get

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(move_if<!std::is_lvalue_reference<Base>{}>(*as_derived));
  } else {
    return visit<Rest...>(std::forward<Base>(base), f);
  }
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Yakk's answer is admirably concise and seems to work, but I ended up going with Barry's answer in practice, because I found that match_cvref_t was much easier to reason about than any of the alternatives. Plus, in my particular case, I ended up needing to refer to match_cvref_t anyway, to do the actual casting operation correctly. Thus:

template<class Base, class F>
void visit(Base&&, const F&) {
    throw std::bad_cast();
}

template<class DerivedClass, class... Rest, class Base, class F>
void visit(Base&& base, const F& f) {
    if (typeid(base) == typeid(DerivedClass)) {
        using Derived = match_cvref_t<Base, DerivedClass>;
        return f(std::forward<Derived>(static_cast<Derived&&>(base)));
    } else {
        return visit<Rest...>(std::forward<Base>(base), f);
    }
}

I did manage to shrink match_cvref_t down to

template<class From, class To>
using match_cvref_t = match_ref_t<
    From,
    match_cv_t<
        std::remove_reference_t<From>,
        std::remove_reference_t<To>
    >
>;

where match_cv_t and match_ref_t take about 5 lines of code each.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159