Reputation: 4685
I would like to store a std::function in a class as a member.
I have troubles with below test code when calling a.callMethod() where the method has been set just before with a.setMethod(). The code works fine if I remove the template.
I have tried to debug with a function callMethodOutsideClass without success.
Is there a better way to manage that ?
#include <iostream>
#include <vector>
#include <functional>
template<typename T>
struct A
{
A(size_t size, T value) : vec_(size, value), method_(nullptr) {}
void setMethod(const std::function<int(A<T>&)> method) { method_ = method; }
int callMethod()
{
if(method_)
return method_(*this);
else
std::cerr << "method not initialized" << std::endl;
return -1;
}
std::vector<int> vec_;
std::function<int(A<T>& a)> method_;
};
template<typename T>
int callMethodOutsideClass(struct A<T>& a, const std::function<int(A<T>&)> method)
{
return method(a);
}
template<typename T>
int apple(struct A<T>& a)
{
a.vec_[0] += 1;
return 1;
}
template<typename T>
int orange(struct A<T>& a)
{
a.vec_[0] += 2;
return 2;
}
int main()
{
A<int> a(10,4), b(10,4);
std::cout << callMethodOutsideClass(a, &apple) << std::endl;
a.setMethod(&orange);
std::cout << a.callMethod() << std::endl;
std::cout << a.vec_[0] << std::endl;
}
I currently get the following errors :
Foo6.cpp: In function ‘int main()’:
Foo6.cpp:46:47: error: cannot resolve overloaded function ‘apple’ based on conversion to type ‘std::function<int(A<int>&)>’
std::cout << callMethodOutsideClass(a, &apple) << std::endl;
^
Foo6.cpp:48:21: error: no matching function for call to ‘A<int>::setMethod(<unresolved overloaded function type>)’
a.setMethod(&orange);
^
Foo6.cpp:48:21: note: candidate is:
Foo6.cpp:9:7: note: void A<T>::setMethod(std::function<int(A<T>&)>) [with T = int]
void setMethod(const std::function<int(A<T>&)> method) { method_ = method; }
^
Foo6.cpp:9:7: note: no known conversion for argument 1 from ‘<unresolved overloaded function type>’ to ‘std::function<int(A<int>&)>’
Upvotes: 1
Views: 1565
Reputation: 48517
A pointer to function is not a std::function<T>
. The std::function<T>
signature can't be deduced based on the function address given as an argument. In addition, the compiler can't resolve a proper function template specialization to get its address when a conversion to std::function<T>
is requested, since the constructor of std::function<T>
is a function template as well.
You need to be more explicit:
std::cout << callMethodOutsideClass<int>(a, &apple<int>) << std::endl;
// ^^^^^ ^^^^^
a.setMethod(&orange<int>);
// ^^^^^
Is there any way to deduce templates parameters "easily" ?
You can modify the signature of callMethodOutsideClass
in one of two ways:
Disable a type deduction on a std::function<int(A<T>&)>
parameter:
template <typename T> struct identity { using type = T; };
template<typename T>
int callMethodOutsideClass(A<T>& a, const typename identity<std::function<int(A<T>&)>>::type method)
{
return method(a);
}
But you'll have to pay for the type-erasure applied by a std::function
.
Let the compiler deduce the real type of a functor object given as an argument:
template <typename T, typename F>
int callMethodOutsideClass(A<T>& a, F&& method)
{
return std::forward<F>(method)(a);
}
In both cases you can just say:
callMethodOutsideClass(a, &apple<int>);
// ^^^^^
Note: You still have to pass the address of a concrete function template specialization by providing a list of template arguments &apple<int>
. If you want to get away with a simple &address
syntax, then the function taking it needs to declare an exact type of that argument:
template<typename T>
int callMethodOutsideClass(A<T>& a, int(*method)(A<T>&))
{
return method(a);
}
callMethodOutsideClass(a, &apple);
or you could help the compiler resolve the proper overload at a call site:
callMethodOutsideClass(a, static_cast<int(*)(decltype(a)&)>(&apple));
...or, you can use a lambda expression defined as follows:
template<typename T, typename F>
int callMethodOutsideClass(struct A<T>& a, F&& method)
{
return std::forward<F>(method)(a);
}
// in C++11:
callMethodOutsideClass(a, [](decltype(a)& x){return apple(x);});
// in C++14:
callMethodOutsideClass(a, [](auto&& x){return apple(std::forward<decltype(x)>(x));});
As far as the setMethod
member function is concerned, the things are easier, since the compiler knows exactly that it expects const std::function<int(A<T>&)> method
where T
is known (not deduced). So basically, you just need to help the compiler to get the address of a function template specialzation you need at the call site:
a.setMethod(&orange<int>);
a.setMethod(static_cast<int(*)(decltype(a)&)>(&orange));
Upvotes: 3