0

I've got some (vastly simplified) code along the lines of

inline Type f(const int*) { return Type::Int; }
inline Type f(const double*) { return Type::Double; }
inline void g(int) { }
inline void g(double) { }

However, the type is managed at run-time:

enum class Type { Int, Double } ;

I'd like a utility routine to "convert" from the run-time Type to a template to avoid a lot of code duplication.

void do_call_f(Type t)
{
    if (t == Type::Int)
    {
        const auto result = f(static_cast<const int*>(nullptr));
    }
    if (t == Type::Double)
    {
        const auto result = f(static_cast<const double*>(nullptr));
    }
}

void do_call_g(Type t)
{
    if (t == Type::Int)
    {
        g(123);
    }
    if (t == Type::Double)
    {
        g(1.23);
    }
}

int main()
{
    do_call_f(Type::Int);
    do_call_f(Type::Double);

    do_call_g(Type::Int);
    do_call_g(Type::Double);
}

But that's a lot of boiler-plate code in do_call_f() and do_call_g(). How can I remove that with a utility routine? No amount of template "magic" seems to work :-(

I'd like something like (pseudo-code)

template<typename TFunc, typename TArg>
auto invoke(Type t, TFunc func, TArg&& arg)
{
   if (t == Type::Int)
      return func<char>(std::forward<TArg>(arg));
   if (type == Type::Double)
      return func<double>(std::forward<TArg>(arg));
   // ...
}

For my actual code, this should be C++14; but an "I'm curious" answer for C++23 would be acceptable.

  • Which version of C++ are you targeting? – Holt Mar 27 '23 at 14:27
  • Thanks, can you clarify what you want to achieve? Like do you need that intermediate `call_f` (because in your actual code it does something), or can this be removed? – Holt Mar 27 '23 at 14:31
  • 2
    This seems like an XY Problem. What are you actually trying to do? – NathanOliver Mar 27 '23 at 14:34
  • @NathanOliver `f()` and `g()` are template functions; I'd like a "nice" way to call them given a runtime `Type` ... w/o duplicating a lot of code. – Blake Whitmore Mar 27 '23 at 14:35
  • @Holt updated again ... – Blake Whitmore Mar 27 '23 at 14:36
  • But `call_f` already knows the types from `F` and/or `TArg`, so the `t` parameter isn't helpful. There are things we can do in `funcs`, though. – aschepler Mar 27 '23 at 14:39
  • Your code doesn't show "`f()` and `g()` are template functions"... – aschepler Mar 27 '23 at 14:39
  • @aschepler because if I make them templates, I can't figure out how to call them via `call_f()`. – Blake Whitmore Mar 27 '23 at 14:41
  • This switch statement in `call_f` looks obsolete. – Marek R Mar 27 '23 at 14:41
  • 1
    It sounds like what you need is to wrap the call into a lambda. Also note that `t` in `call_f` is useless. Both cases do the same thing so there is no reason to have it. – NathanOliver Mar 27 '23 at 14:42
  • Code demonstrating what you have but including a small invalid piece or a blank with `// how can I do this?` could be helpful. – aschepler Mar 27 '23 at 14:42
  • 1
    Are you trying to implement `std::variant` and `std::visit`? – Marek R Mar 27 '23 at 14:43
  • Have you noticed that C++ has quite a meta-programming library for helping with templates? https://en.cppreference.com/w/cpp/meta – Öö Tiib Mar 27 '23 at 14:43
  • You can use a `static_cast` or a lambda with a given parameter to use the right implementation of your templated `f` and `g` (see https://godbolt.org/z/seWqoEGfj), although as already mentioned, this looks like an XY-problem. – Holt Mar 27 '23 at 14:43
  • @BlakeWhitmore Here is an example of wrapping an overloaded function in a lambda and passing it to a function that takes a functor and its arguments: https://stackoverflow.com/questions/54182502/call-a-functor-with-a-specific-function-from-an-overload-set/54182576#54182576 – NathanOliver Mar 27 '23 at 14:47
  • @aschepler updated ... I'm trying to reduce the boilerplate code in `do_call_f()` and `do_call_g()` – Blake Whitmore Mar 27 '23 at 14:50
  • @NathanOliver I'm trying to get rid of the duplicated boilerplate code. – Blake Whitmore Mar 27 '23 at 14:59
  • What duplicate code? You have different values in each if statement in `call_f` and `call_g`. – NathanOliver Mar 27 '23 at 15:07
  • @NathanOliver the repeated `if` checks against the runtime `Type` in `do_call_f()` and `do_call_g()`. I'd like to pass a function (`f()` or `g()`) and a type (`int` or `double`) to some utility routine. – Blake Whitmore Mar 27 '23 at 15:13

1 Answers1

0

C++ doesn't provide any way to directly pass a function template or overload set into another function. (The name of an overload set can be used as an argument, but that always ends up passing just one specific function.)

But as @NathanOliver hinted in a comment, in C++14 or later you can encapsulate an overload set using a generic lambda: [](const auto *ptr) { f(ptr); } is a class-type object which can be passed to a function template.

// Some boilerplate:
#include <utility>
#include <stdexcept>

enum class Type { Int, Double };
template <Type T> struct TypeTag;
template <> struct TypeTag<Type::Int> {
    using type = int;
    static constexpr Type value = Type::Int;
};
template <> struct TypeTag<Type::Double> {
    using type = double;
    static constexpr Type value = Type::Double;
};
#define TAG_TYPE(tag) typename decltype(tag)::type

template <class F, typename ...Args>
decltype(auto) call_with_tag(Type t, F&& func, Args&& ...args) {
    switch (t) {
    case Type::Int:
        return std::forward<F>(func)(TypeTag<Type::Int>{}, std::forward<Args>(args)...);
    case Type::Double:
        return std::forward<F>(func)(TypeTag<Type::Double>{}, std::forward<Args>(args)...);
    }
    throw std::invalid_argument("Bad Type enum");
}

// end boilerplate

Type f(const int*);
Type f(const double*);
void g(int);
void g(double);
template <class T> T get_g_arg();

Type do_call_f(Type t) {
    auto f_tag = [](auto tag) {
        return f(static_cast<const TAG_TYPE(tag)*>(nullptr));
    };
    return call_with_tag(t, f_tag);
}

void do_call_g(Type t) {
    auto g_tag = [](auto tag) {
        g(get_g_arg<TAG_TYPE(tag)>());
    };
    call_with_tag(t, g_tag);
}
aschepler
  • 70,891
  • 9
  • 107
  • 161