【问题标题】:Dynamically cast to function argument type (C++)动态转换为函数参数类型 (C++)
【发布时间】:2021-04-19 21:18:11
【问题描述】:

f1() 中,我希望能够将其foo 参数动态转换为与f2() 的参数相同的类型(BarQux 等)。这可能吗?

struct Foo {
  virtual ~Foo() = default;
};
struct Bar : public Foo {};
struct Qux : public Foo {};

template<class T>
void f1(T f2, Foo &foo) {
  // dynamically cast foo to type of f2's argument?
  f2(dynamic_cast<Bar &>(foo));
}

int main() {
  Bar bar;
  Qux qux;
  f1([](Bar &bar) {}, bar);
  f1([](Qux &qux) {}, qux); // error here!
}

【问题讨论】:

  • 这是我今年迄今为止最喜欢的问题之一。表面上看起来很简单,但是......不 - 看看所有不错的答案。全方位 +1。

标签: c++ c++11 templates casting dynamic-cast


【解决方案1】:

这是一个模板化转换的工作operator

template <typename T>
struct dynamic_caster
{
    T *ptr;
    dynamic_caster(T &ref) : ptr(&ref) {}
    template <typename U>
    operator U &() const
    {
        return *dynamic_cast<std::remove_reference_t<U> *>(ptr);
    }
};

template<class T>
void f1(T f2, Foo &foo)
{
    f2(dynamic_caster{foo});
}

【讨论】:

  • 可能最好将成员更改为引用并重命名为dynamic_deducer,但是头部爆炸干得好!
  • 如果我以f2 的形式传入函子,这是否也有效?
  • @Jack 你已经传递了一个函子。只有当函子的operator() 不是模板并且可能也没有重载时,它才会起作用。
  • 谢谢@HolyBlackCat。即使operator() 没有重载,我实际上也无法让它与函数对象一起使用。不过也没什么大不了!再次感谢您的帮助。
  • 是的,如果 operator() 超载(与我的回答相同的限制),所有赌注都将关闭。 @MooingDuck 的答案似乎是最优雅的,除了需要编写几个个案功能。好吧,谁知道呢,模板和继承不能很好地结合在一起。
【解决方案2】:

我努力通过推导有状态 lambda 的成员函数指针来做到这一点,但最终成功使用了 SFINAE,它更冗长,但适用于有状态 lambda。

template<class T>
auto f1(T f2, Foo &foo) -> decltype(f2(dynamic_cast<Bar&>(foo))) {
    return f2(dynamic_cast<Bar&>(foo));
}
template<class T>
auto f1(T f2, Foo &foo) -> decltype(f2(dynamic_cast<Qux&>(foo))) {
    return f2(dynamic_cast<Qux&>(foo));
}

http://coliru.stacked-crooked.com/a/61ceb268b0a5a443

【讨论】:

  • 这实际上表明OP并不真正需要推断参数的类型,它需要知道某个参数是否有效,这是一个更简单的任务。
【解决方案3】:

你可以编写一个函数模板来推断函数指针的参数类型:

template<typename Ret, typename Arg>
Arg arg(Ret(*)(Arg));

然后在进行强制转换时使用它来确定f2 的参数类型:

f2(dynamic_cast<decltype(arg(+f2))>(foo));

请注意,+ 在将其作为参数传递给 arg 时会将 lambda 衰减为函数指针。这仅在 lambda f2 只有一个参数且没有任何捕获时才有效。

【讨论】:

【解决方案4】:

你不能这样做,至少不能直接这样做。 问题是不受约束的类型T(函数对象的类型)可能对operator() 有几个重载。 它甚至可能是一个模板化的运算符。 这也适用于 lambda(甚至可能是 auto lambda)。

换句话说,即使有办法,现在 C++ 中也没有足够的自省来做到这一点。 IMO,即使你可以,它也可能表明设计存在缺陷。 (@MooingDuck 的答案设计似乎更有希望。)

所以你必须稍微改变你正在做的事情。 例如,您的 lambda 似乎是无状态的,这意味着您可以:

  1. 使用免费函数。
  2. 将(无状态)lambda 衰减为函数指针(提示,lambdas 衰减 (ab) 使用 + 操作)。

一旦你接受了这一点,那就很容易了; 您可以使用Boost.TypeTraits 推导出函数的(现在唯一的)定义参数。

#include <boost/type_traits.hpp>

struct Foo {
  virtual ~Foo() = default;
};
struct Bar : public Foo {};
struct Qux : public Foo {};

template<class T>
void f1(T f2, Foo &foo) {
  // dynamically cast foo to type of f2's argument
  f2(dynamic_cast<typename boost::function_traits<decltype(*f2)>::arg1_type&>(foo));
}

int main() {
  Bar bar;
  Qux qux;
  f1(+[](Bar &bar) {}, bar);
  f1(+[](Qux &qux) {}, qux); // works now!
}

https://godbolt.org/z/r1jf9n8Yf

如果您想要一个更通用的解决方案,您已经定义了一个带有内部或 sfinae 检测到的“第一个参数”的函数对象协议。例如struct F{using arg1_type = ...; auto operator()(arg1_type) const{...}};.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-21
    • 2020-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多