anom1
anom1

Reputation: 113

How to call a function from a std::variant of different return type function pointers?

How can you call a function from a std::variant of different return type function pointers?

For example, if I dont use std::variant (assuming the function return type is void):

void print()
{
    std::cout << "Hello World!" << std::endl;;
}

void run_func(void (*func)())
{
    func();
}

int main()
{
    run_func(print);
}

Output:

Hello World! (as expected)

How can this be done while using a std::variant of return types, so that i can call functions of different return types.

For example:

void print()
{
    std::cout << "Hello World!" << std::endl;
}

void run_func(std::variant<void (*)(), int (*)(), unsigned int (*)()> func)
{
    func(); // will return whatever type it is (void in this case)
}

int main()
{
    run_func(print);
}

The code above is non-functional but gives an idea of what im trying to explain.

Upvotes: 1

Views: 1718

Answers (3)

AndyG
AndyG

Reputation: 41100

You must visit your variant:

void run_func(std::variant<void (*)(), int (*)(), unsigned int (*)()> variant)
{
    std::visit([](auto fptr){fptr();}, variant);
}

Live C++17 demo


If you actually want to get the int or unsigned int back out, we can return another variant as a result of calling run_func. I'll be using C++20 because it's more straightforward thanks to template parameter support for lambdas:

auto run_func(std::variant<void (*)(), int (*)(), unsigned int (*)()> variant)
 -> std::variant<std::monostate, int, unsigned int>
{
    using ret_t = std::variant<std::monostate, int, unsigned int>;
    return std::visit([]<class Ret>(Ret (*fptr)()) -> ret_t
    {
        if constexpr (std::is_same_v<void, Ret>)
        {
            fptr();
            return std::monostate{};
        }
        else
        {
            return fptr();
        }
    }, variant);
}

And we can call it like so:

int get_answer(){return 42;}

// ...

std::visit([](auto i){
        if constexpr(!std::is_same_v<decltype(i), std::monostate>)
            std::cout << i << std::endl;
    }, run_func(&get_answer));

C++ 20 demo

With some more work, we could probably generalize run_func to accept any number of different function pointer types, and return an appropriate variant


EDIT

For fun, I've generalized the code to accept any number of function types, with the restriction that none take any args (we could go that far if we really wanted to, though).

It requires we write a number of utility functions to operate on types:

  • unique-ify a typelist (so our returned variant only contains the types it needs)
  • remove void from a list of types (so that we don't try to return a variant with a void type)

Without further ado, here is that boilerplate:

template<class...>
struct typelist{};

template<class... T, class... U>
auto concat(typelist<T...>, typelist<U...>) -> typelist<T..., U...>;

// reduce the typelist to the set of unique types
auto unique(typelist<>) -> typelist<>;

template<class Head, class... Tail>
auto unique(typelist<Head, Tail...>)
 -> std::conditional_t<
        std::disjunction_v<std::is_same<Head, Tail>...>, // predicate
        decltype(unique(std::declval<typelist<Tail...>>())), // true scenario: remove Head from typelist
        // false scenario, keep head, recurse on Tail and then concat to Head
        decltype(concat(std::declval<typelist<Head>>(),
             unique(std::declval<typelist<Tail...>>())))
        >; // conditional

template<class... T>
using unique_typelist = decltype(unique(std::declval<typelist<T...>>())); 

// remove the "type" void from the typelist
auto remove_void(typelist<>) -> typelist<>;

template<class Head, class... Tail>
auto remove_void(typelist<Head, Tail...>)
-> std::conditional_t<
    std::is_same_v<Head, void>, // predicate
    // true scenario: remove Head from typelist and recurse on Tail
    decltype(remove_void(std::declval<typelist<Tail...>>())),
    // false scenario: keep Head, recurse on Tail, then concat
    decltype(concat(std::declval<typelist<Head>>(), remove_void(std::declval<typelist<Tail...>>())))
    >;

template<class... T>
using typelist_without_void = decltype(remove_void(std::declval<typelist<T...>>())); 

template<class... T>
using unique_typelist_without_void = decltype(remove_void(unique(std::declval<typelist<T...>>()))); 

// make a variant from whatever is in the typelist
template<class... T>
auto make_variant(typelist<T...>) -> std::variant<T...>;

// make a variant from a typelist, but make the typlist unique and remove void from it first
template<class... T>
auto variant_from_typelist(typelist<T...>) -> decltype(make_variant(std::declval<unique_typelist_without_void<std::monostate, T...>>()));

template<class... T>
using variant_from_types = decltype(variant_from_typelist(std::declval<typelist<T...>>()));

For convenience, we'll add a type alias for function pointers as well as write a call-through for run_func so we can accept either a variant or a function pointer:


template<class T>
using fn_type = T(*)();

template<class... Ret>
auto run_func_impl(const std::variant<fn_type<Ret>...>& variant)
 -> variant_from_types<Ret...>
{
    return std::visit([]<class R>(R (*fptr)()) -> variant_from_types<Ret...>
    {
        if constexpr (std::is_same_v<void, R>)
        {
            fptr();
            return std::monostate{};
        }
        else
        {
            return fptr();
        }
    }, variant);
}

template<class R>
auto run_func(fn_type<R> fn) 
{
    return run_func_impl(std::variant<decltype(fn)>(fn));
}

template<class... Ret>
auto run_func(const std::variant<fn_type<Ret>...>& variant)
{
    return run_func_impl(variant);
}

Our test functions that we'll get pointers to for testing:

void print()
{
    std::cout << "Hello World!" << std::endl;
}

int get_answer(){return 42;}

unsigned int third_function(){return 1337U;}

And finally, our tests:

auto visitor = [](auto i){
 if constexpr(!std::is_same_v<decltype(i), std::monostate>)
     std::cout << i << std::endl;
};
std::visit(visitor, run_func(&print));
std::visit(visitor, run_func(&get_answer));

std::variant<void(*)(), int(*)(), unsigned int(*)()> v(&print);
std::visit(visitor, run_func(v));

std::variant<void(*)(), int(*)(), unsigned int(*)()> v2(&get_answer);
std::visit(visitor, run_func(v2));

std::variant<void(*)(), int(*)(), unsigned int(*)()> v3(&third_function);
std::visit(visitor, run_func(v3));

Generic code demo (C++20)

Upvotes: 3

jacky la mouette
jacky la mouette

Reputation: 532

You could use a placeholder parameter with auto:

#include <iostream>
#include <functional>

void print()
{
    std::cout << "Hello World!" << std::endl;;
}

void print_int(int i)
{
    std::cout << i << std::endl;
}

void run_func(auto f)
{
    f();
}

int main()
{
    run_func(print);
    run_func(std::bind(print_int, 1));
}

Output:

Hello World!
1

EDIT: just realized you asked specifically about std::variant, sorry, my answer might not be relevant

Upvotes: 0

lorro
lorro

Reputation: 10880

std::variant<> contains exactly one of the types listed, and the type stored is known (and can be changed) runtime. Therefore, you cannot make the return type dependent on it, unless that's also a variant (void intentionally skipped):

std::variant<int, unsigned> run_func(int (*)(), unsigned int (*)()> func)
{ { std::visit([&](auto f) -> std::variant<int, unsigned> { return f(); }, func); }
 }

However, if all you need is to be able to process different outputs from a function, it's sometimes beneficial to use continuation passing style. It means, instead of returning, you accept a lambda that processes the output:

template<typename Cont>
void run_func(int (*)(), unsigned int (*)()> func, Cont cont)
{ std::visit([&](auto f) { cont(f); }, func); }

Upvotes: 2

Related Questions