Dynamically cast to function argument type (C++)

Viewed 135

In f1(), I want to be able to dynamically cast its foo argument to the same type as the argument of f2() (Bar, Qux, etc). Is this possible?

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!
}
4 Answers

This is a job for a templated conversion 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});
}

I struggled to do this with deduced member function pointers for stateful lambdas, but eventually succeeded with SFINAE, which is more verbose, but works with stateful lambdas.

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

You can write a function template that deduces the argument type of a function pointer:

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

and then use this to figure out the argument type of f2 when doing the cast:

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

Note that the + when passing it as an argument to arg is to decay the lambda to a function pointer. This will only work when the lambda f2 has exactly one argument, and doesn't have any captures.

You cannot do that, not directly at least. The problem is that an unconstrained type T (type of the function object), might have several overloads for operator(). It might even be a templated operator. That applies to a lambdas also (might even be an auto lambda).

In other words, even if there can be way, there is non enough introspection in C++ to do this right now. And IMO even if you could it might indicate a flaw in the design. (@MooingDuck's answer design seem more promising.)

So you have to change slightly what you are doing. For example it seems that your lambdas are stateless, meaning that you can :

  1. use a free function.
  2. decay the (stateless) lambda to a function pointer (tip, lambdas decay (ab)using the + operation).

Once you accept that, it is easy from there; you can use Boost.TypeTraits to deduce the (now uniquely) defined argument of the function.

#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

If you want a more general solution you have define a protocol of function object with an internal or sfinae-detected "first argument". e.g. struct F{using arg1_type = ...; auto operator()(arg1_type) const{...}};.

Related