Vector of Std::Function with Different Signatures

Vector of std::function with different signatures

You haven't said what you expect to be able to do with func2 after putting it in a vector with the wrong type.

You can easily use std::bind to put it in the vector if you know the arguments ahead of time:

const std::vector<std::function<void(std::string)>> functions
{
func1,
std::bind(func2, std::placeholders::_1, 5, 6)
};

Now functions[1]("foo") will call func2("foo", 5, 6), and will pass 5 and 6 to func2 every time.

Here's the same thing using a lambda instead of std::bind

const std::vector<std::function<void(std::string)>> functions
{
func1,
[=](const std::string& s){ func2(s, func2_arg1, func2_arg2); }
};

If you don't know the arguments yet, you can bind references to some variables:

int func2_arg1 = 5;
int func2_arg2 = 6;
const std::vector<std::function<void(std::string)>> functions
{
func1,
std::bind(func2, std::placeholders::_1, std::ref(func2_arg1), std::ref(func2_arg2))
};

Now functions[1]("foo") will call func2("foo", func2_arg1, func2_arg2), and you can assign new values to the integers to pass different arguments to func2.

And using a lambda function instead of std::bind

const std::vector<std::function<void(std::string)>> functions
{
func1,
[&](const std::string& s){ func2(s, func2_arg1, func2_arg2); }
};

This is pretty ugly though, as you need to keep the int variables around for as long as the callable object (the closure or the bind expression) referring to them exists.

Vector of functions, different signature and parameters

You can use std::apply to pass a tuple on as the arguments to a function.

For storing the functions, you need some kind of type erasure. I opted for std::any in this example.

To store the functions with an ID, i used a std::map.

#include <iostream>
#include <functional>
#include <any>
#include <map>
#include <tuple>

int foo(int val) {
return val;
}

float bar(float val1, int val2) {
return val1 * val2;
}

void zor(int i) {
std::cout << i << '\n';
}

struct FuncCollection {
std::map<int, std::function<std::any(std::any)>> funcMap;

template <typename Ret, typename... Args>
void addFunc(int id, Ret (*fPtr)(Args...)) {
funcMap[id] = [=](std::any args) -> std::any {
auto tuple = std::any_cast<std::tuple<Args...>>(args);
if constexpr(std::is_same_v<Ret, void>) {
std::apply(fPtr, tuple);
return 0;
} else {
return std::apply(fPtr, tuple);
}
};
}

template <typename... Args>
auto callFunc(int id, std::tuple<Args...> args) {
return funcMap[id](args);
}
};

int main()
{
FuncCollection fc;
fc.addFunc(1, foo);
fc.addFunc(2, bar);
fc.addFunc(3, zor);

std::tuple<int> p1{1};
std::tuple<float, int> p2{3.14, 2};
std::tuple<int> p3{5};

auto r1 = fc.callFunc(1, p1);
auto r2 = fc.callFunc(2, p2);
fc.callFunc(3, p3);

std::cout << std::any_cast<int>(r1) << ' ' << std::any_cast<float>(r2) << '\n';
}

This is just an example, and it especially lacks sufficient error checks. std::any_cast will throw an exception on an invalid cast.

c++: std::vector of std::function with arbitrary signatures

This should work:

#include <iostream>
#include <functional>
#include <vector>

void hello() { std::cout << "Hello\n"; }
void hello2(const char * name) { std::cout << "Hello " << name << '\n'; }

int main()
{

std::vector<std::function<void()>> v;
v.push_back(hello);
v.push_back(std::bind(hello2, "tim"));
v[0]();
v[1]();
}

How are you doing it?

std::function accepting different function signatures in c++

Here is a simple C++ broadcaster:

using token = std::shared_ptr<void>;

template<class...Ts>
struct broadcaster {
using listen = std::function<void(Ts...)>;
using sp_listen = std::shared_ptr<listen>;
using wp_listen = std::weak_ptr<listen>;

token attach( listen l ) {
return attach( std::make_shared<listen>(std::move(l)) );
}
token attach( sp_listen sp ) {
listeners.push_back(sp);
return sp;
}
void operator()(Ts...ts)const {
listeners.erase(
std::remove_if( begin(listeners), end(listeners),
[](auto&& wp){return !(bool)wp.lock();}
),
end(listeners)
);
auto tmp = listeners;
for (auto&& l : tmp) {
if (auto pf = l.lock()) {
(*pf)(ts...);
}
}
}
private:
mutable std::vector<wp_listen> listeners;
};

To listen to it, you .attach and pass it a function to call. attach returns a token, and the function is invoked so long as that token (or copies of it) continue to exist.

To invoke the message, you invoke () on the broadcaster.

Memory of dead callbacks is reclaimed the next time you invoke the broadcaster; indirectly owned resources are cleaned up faster.

If you register a listener while it is currently broadcasting, it won't get the current broadcast.

You can add a std::mutex to make this usable from multiple threads at once, or externally synchronize. If you synchronize internally, I wouldn't hold the mutex as you run the for(auto&& loop in () to avoid reentrancy problems.

Example use:

struct location {
int x, y;
};
struct button {
broadcaster< location > mouse_click;
broadcaster<> mouse_enter;
broadcaster<> mouse_leave;
};

struct dancer {
std::vector<token> listen_tokens;
dancer( button& b ) {
listen_tokens.push_back( b.mouse_enter.attach([this]{ dance(); } ) );
listen_tokens.push_back( b.mouse_leave.attach([this]{ end_dance(); } ) );
listen_tokens.push_back( b.mouse_click.attach(
[this](location l){
pose(l.x, l.y);
}
) );
}
void dance() const {
std::cout << "start dancing\n";
}
void pose( int x, int y ) const {
std::cout << "struck a pose at " << x << ", " << y << "\n";
}
void end_dance() const {
std::cout << "end dancing\n";
}
};

Note that no virtual methods where used. The only polymorphism was type erasure based on std::function.

Listening objects have to track the lifetime they which to listen over explicitly (by keeping a token alive), and if they want to be able to unregister to a specific broadcaster they have to keep that association themselves.

If broadcasters go away first, there is no problem. If listeners go away, so long as their token preceeds them everything is ok. Attaching does cause a dynamic allocation to store the token (and a moved into copy of the listener), but only one.

This is a different approach than you'd use in C#, both because it relies on RAII and because it is not OO at its core, yet remains polymorphic.

Array of functions with different signatures

I found this workaround for this problem:

#include <iostream>
#include <vector>

class Foo
{
};

class Foo1 : public Foo
{
};

class Foo2 : public Foo
{
};

class Foo3 : public Foo
{
};

void f1(Foo1*)
{
std::cout<<"f1\n";
}

void f2(Foo2*)
{
std::cout<<"f2\n";
}

void f3(Foo3*)
{
std::cout<<"f3\n";
}

template<typename T>
void AddPointer(std::vector<typename void (*)(Foo*)>& fPointers, T function)
{
fPointers.push_back(reinterpret_cast<void (*)(Foo*)>(function));
}

void main()
{
std::vector<typename void (*)(Foo*)> fPointers;

AddPointer(fPointers, f1);
AddPointer(fPointers, f2);
AddPointer(fPointers, f3);

Foo1 foo1;
Foo2 foo2;
Foo3 foo3;

fPointers[0](&foo1);
fPointers[1](&foo2);
fPointers[2](&foo3);
}

How to map different function signatures into the same std::map?

If I understand the comments correctly, the std::map<Key, Value> is only part of the problem. You first need the Value part - what C++ type can hold the overload set of f?

Jarod's comment is right: You basically need [](auto... args){ return foo(args...)};. This is a single lambda object, with an overloaded operator(). Each overload of operator() selects one of your foo() overloads.

This then shows the ultimate problem. [](auto... args){ return bar(args...)}; is another lambda, with a different type, so it can't go in the same std::map<Key, Value>.

That's really no surprise. When the compiler sees baz["foo"](qux), it needs Value::operator(std::string). That can be a template instantiation, but an instantiation cannot happen at runtime. So Value can't depend on "foo". C++ simply does not have run-time overloading, that is not how the language works.

@JesperJuhl may have a point that this is an XY problem. What if you had foo(std::variant<int, std::string, std::vector<int>>) ? This is not an overload set; this is a single function. Behind the scenes, it can dispatch to foo_impl(...) in any way you like.

Passing vector with std::move function signature

My question is, isnt the function foo supposed to have signature void foo(std::vector<int>&& v) for it to be able to take the r value reference of the vector?

If that's what you want, then yes, but that doesn't mean what you have is incorrect. When you pass something to a function it copy initializes the parameter from the source. That means if you do

vector<int> v1 = {1,2,3};
foo(v1);

then foo gets a copy of v1. With

vector<int> v1 = {1,2,3};
foo(std::move(v1));

We copy initialize v from std::move(v1) and since std::move(v1) is an rvalue reference, the move constructor is chosen for v and v1 gets moved into the function.

So, by taking by value you give the option to the caller to give it a temporary, give it a rvalue reference, which will both move the object into the function, or just let a copy happen. If you had void foo(std::vector<int>&& v) Then you could only pass a temporary or std::move() an lvalue. There would be no way to allow the caller to have a copy made without them making one themselves and then moving that copy into the function.

How to write function signature for accepting std::vector of any type?

You can go simple and use auto deduction:

template <typename Container>
void print(const Container& c)
{
for (const auto& e : c) cout << e << ' ';
cout << endl;
}

This will accept any container for any type with defined operator<< for its elements.

One could also use type traits to enable/disable overloads for different containers.

For example, lets say we have type traits IsContainer defined as below.
Then one could do something like:

template <typename Container,
typename = enable_if_t<IsContainer<Container, std:::vector>::value>>
void print(const Container& c)
{
for (const auto& e : c) cout << e << ' ';
cout << endl;
}

with IsContainer type traits available to check for specific container type:

template<typename Test, template<typename...> class Ref>
struct IsContainer: std::false_type {};

template<template<typename...> class Ref, typename... Args>
struct IsContainer<Ref<Args...>, Ref>: std::true_type {};

This would be usable if you'd need more such checks in your project.

By using OR'ing one could also enable this for different containers with not replicating the code. For example to enable printing for vector and list:

 template <typename Container,
typename = enable_if_t<
IsContainer<Container, std:::vector>::value
|| IsContainer<Container, std::list>::value>>
void print(const Container& c)
{
for (const auto& e : c) cout << e << ' ';
cout << endl;
}


Related Topics



Leave a reply



Submit