在铸造参数

时间:2017-05-23 19:45:29

标签: c++ c++14 visitor perfect-forwarding

这是与Using std::forward on sub fields类似的问题,但答案似乎并不适用于我的情况。

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

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

此处编写的此代码的问题是std::forward<Base>(*as_derived)不仅仅更改*as_derived上的ref-qualifiers和const-qualifiers以实现完美转发;它实际上将类型强制转换为Base&,使visit的整个点变得紧张!

是否有一个标准库函数可以执行我想要 std::forward要做的事情 - 即更改*as_derived上的ref-qualifiers和const-qualifiers以匹配这将是从std::forward<Base>

完美推断出来的。

如果没有标准库函数,我怎么能写一个“完美转发子类型”函数供我自己使用?

上面的Wandbox链接包含一些“适用于此测试用例”的内容,但它不保留常量,并且它看起来并不优雅。

3 个答案:

答案 0 :(得分:4)

标准中没有任何内容。但是写起来并不难。只是烦人你需要做的是编写一个特性,为你提供传递给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;

答案 1 :(得分:2)

前进只是一个有条件的举动。

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

答案 2 :(得分:0)

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行代码。