在转换参数上使用 std::forward

Using std::forward on casted arguments

这是一个与 类似的问题,但那里的答案似乎不适用于我的情况。

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

我的目标是以下测试用例工作:

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

期望的输出:"meow""woof""MEOW!""WOOF!"。请注意函数 speakyowl 是非虚拟的;这在我的原始代码中是必需的,因为它们实际上是模板,而模板不能是虚拟的。

此处编写的代码的问题在于 std::forward<Base>(*as_derived) 不仅更改 *as_derived 上的 ref-qualifiers 和 const-qualifiers 以启用完美转发;它实际上将 type 重新转换为 Base&,削弱了 visit!

的全部意义

是否有一个标准库函数可以执行我想要 std::forward 做的事情——即更改 [=16= 上的 ref-qualifiers 和 const-qualifiers ] 以匹配从 std::forward<Base>?

中完美向前推导出的那些

如果没有标准库函数,我怎么写一个"perfect forward a child type"函数供自己使用?

上面的 Wandbox link 包含此测试用例 "works" 的内容,但它不保留常量并且看起来一点也不优雅。

标准中没有这方面的内容。但是不难写。只是烦人。您需要做的是编写一个特征,为您提供要传递给 forward 的类型 - 基本上您想要将 Derived 的 cv 资格和引用与 Base 相匹配, 然后将该类型传递给 forward:

return f(std::forward<match_ref_t<Base, Derived>>(*as_derived));

一个简单的实现,几乎肯定可以变得更简洁,就是:

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

或者,我猜:

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;

前进只是有条件的举动。

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

现在我们得到

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 的回答非常简洁而且似乎有效,但我最终在实践中采用了 Barry 的回答,因为我发现 match_cvref_t 比任何其他选择都更容易推理。另外,在我的特殊情况下,无论如何我最终都需要参考 match_cvref_t 才能正确执行实际的转换操作。因此:

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

我确实设法将 match_cvref_t 缩小到

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

其中 match_cv_tmatch_ref_t 每个大约需要 5 行代码。