【问题标题】:Using std::forward on casted arguments对强制转换的参数使用 std::forward
【发布时间】:2017-10-23 22:25:12
【问题描述】:

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

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

这里写的这段代码的问题是std::forward&lt;Base&gt;(*as_derived) 不只是改变*as_derived 上的ref-qualifiers 和const-qualifiers 来启用完美转发;它实际上将 type 转换回 Base&amp;,削弱了 visit 的全部意义!

是否有一个标准库函数可以完成我想要 std::forward 做的事情 - 即更改 *as_derived 上的 ref-qualifiers 和 const-qualifiers 以匹配那些完美的——从std::forward&lt;Base&gt;向前推导出来?

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

上面的 Wandbox 链接包含一些对这个测试用例“有效”的东西,但它没有保留 constness,而且看起来一点也不优雅。

【问题讨论】:

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


    【解决方案1】:

    标准中没有任何内容。但是写起来并不难。只是烦人。您需要做的是编写一个特征,为您提供传递给forward 的类型 - 基本上您希望将Derived 的cv-qualifications 和reference-ness 与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;
    

    【讨论】:

      【解决方案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);
        }
      }
      

      【讨论】:

      • 这比枚举所有 cv-ref 的东西要好得多。
      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 2011-11-07
        • 2019-04-11
        • 1970-01-01
        • 2016-03-21
        • 1970-01-01
        • 2011-02-18
        • 2018-10-28
        • 2019-08-26
        • 1970-01-01
        相关资源
        最近更新 更多