This utility function invokes one of a list of functions which are covariant on the provided value. It reduces boilerplate by internally performing dynamic_cast on the input value, and invokes the first function with an input type that was successfully cast. It must be invoked with a template argument indicating the return type. What do you think?
covariant_invoke.hpp
#ifndef UTILITIES_COVARIANT_INVOKE_HPP
#define UTILITIES_COVARIANT_INVOKE_HPP
namespace details {
template <typename T>
struct invoke_type_impl {
static_assert(!std::is_same_v<T, T>, "template instantiation failed");
};
template <typename T, typename U, typename V>
struct invoke_type_impl<T (U::*)(V const &) const> {
typedef V in_t;
typedef T out_t;
};
template <typename T>
struct invoke_type {
typedef typename invoke_type_impl<decltype(&T::operator ())>::in_t in_t;
typedef typename invoke_type_impl<decltype(&T::operator ())>::out_t out_t;
};
}
template<typename U, typename T, typename V1, typename std::enable_if<!std::is_same<U, void>::value, int>::type = 0>
U covariant_invoke(T const & value, V1 const & func1) {
typedef typename details::invoke_type<V1>::in_t in_t;
auto const * asU1 = dynamic_cast<in_t const *>(&value);
if (asU1 != nullptr) {
return func1(*asU1);
}
throw std::runtime_error("no suitable function");
}
template<typename U, typename T, typename V1, typename V2, typename... Vs, typename std::enable_if<!std::is_same<U, void>::value, int>::type = 0>
U covariant_invoke(T const & value, V1 const & func1, V2 const & func2, Vs const &... funcs) {
typedef typename details::invoke_type<V1>::in_t in_t;
auto const * asU1 = dynamic_cast<in_t const *>(&value);
if (asU1 != nullptr) {
return func1(*asU1);
}
return covariant_invoke<U>(value, func2, funcs...);
}
template<typename U, typename T, typename V1, typename std::enable_if<std::is_same<U, void>::value, int>::type = 0>
void covariant_invoke(T const & value, V1 const & func1) {
typedef typename details::invoke_type<V1>::in_t in_t;
static_assert(std::is_same<typename details::invoke_type<V1>::out_t, void>::value, "function does not return void");
auto const * asU1 = dynamic_cast<in_t const *>(&value);
if (asU1 != nullptr) {
func1(*asU1);
return;
}
throw std::runtime_error("no suitable function");
}
template<typename U, typename T, typename V1, typename V2, typename... Vs, typename std::enable_if<std::is_same<U, void>::value, int>::type = 0>
void covariant_invoke(T const & value, V1 const & func1, V2 const & func2, Vs const &... funcs) {
typedef typename details::invoke_type<V1>::in_t in_t;
static_assert(std::is_same<typename details::invoke_type<V1>::out_t, void>::value, "function does not return void");
auto const * asU1 = dynamic_cast<in_t const *>(&value);
if (asU1 != nullptr) {
func1(*asU1);
return;
}
covariant_invoke<U>(value, func2, funcs...);
}
#endif //UTILITIES_COVARIANT_INVOKE_HPP
Example Usage
#include <iostream>
class A {
public:
virtual ~A() = default;
};
class B : public A {
public:
explicit B(int value) : i(value) {}
virtual ~B() = default;
int i;
};
class C : public A {
public:
explicit C(char* value) : strPtr(value) {}
virtual ~C() = default;
char* strPtr;
};
class D : public A {
public:
virtual ~D() = default;
};
B b = B(1337);
C c = C("Hello, world!");
D d;
// test the non-void return overload
void test_non_void_return(A const & a) {
std::cout << covariant_invoke<std::string>(a,
[&](B const & value) { return std::to_string(value.i); },
[&](C const & value) { return value.strPtr; });
}
// test the void return overload
void test_void_return(A const & a) {
covariant_invoke<void>(a,
[&](B const & value) { std::to_string(value.i); },
[&](C const & value) { value.strPtr; });
}
void test() {
test_non_void_return(b);
test_non_void_return(c);
test_non_void_return(d); //will throw, because there is no lambda to handle D values
test_void_return(b);
test_void_return(c);
test_void_return(d); //will throw, because there is no lambda to handle D values
}