问题
Using C++ with g++ 5.4.0 on Ubuntu 16.04.
I have a class A, and a class B that derives from class A. Function f1 takes a shared pointer to class A as parameter. Function f2 takes a shared pointer to class B as parameter and return the same type as f1. Using boost::function, another function F takes a function such as f1 as parameter. The code looks like this :
result_t f1 ( const boost::shared_ptr<A> a );
result_t f2 ( const boost::shared_ptr<B> b );
typedef boost::function < result_t (const boost::shared_ptr<A>&) > f1_t;
void F ( const f1_t f );
Calling F with f1 as argument works fine. I now want to call F but with f2 as argument. I get the following error :
error: invalid initialization of reference of type const boost::shared_ptr<B>& from expression of type const boost::shared_ptr<A>
return f(BOOST_FUNCTION_ARGS);
There's not really a need for F to get this error, doing :
f1_t f = f2;
gives the same error.
回答1:
There's no co-variance for function prototypes. Different signatures are simply that: different types.
In this case you'd need to wrap the function with a converting wrapper.
Let's create a few facility definitions:
using result_t = int;
struct A { };
struct B : A { };
typedef boost::shared_ptr<A> APtr;
typedef boost::shared_ptr<B> BPtr;
result_t f1(APtr) { return 1; }
result_t f2(BPtr) { return 2; }
typedef boost::function <result_t(APtr const&)> funOfA;
typedef boost::function <result_t(BPtr const&)> funOfB;
Now wrapping funOfB
as a funOfA
would look like this:
funOfA wrapFunOfB(const funOfB f) {
struct {
funOfB _f;
result_t operator()(APtr const& a) const {
return _f(boost::static_pointer_cast<B>(a));
}
} wrap { f };
return wrap;
}
Now you can easily write:
int main() {
F(f1);
F(wrapFunOfB(f2));
}
Simple C++03 Demo
Live On Coliru
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/function.hpp>
#include <iostream>
typedef int result_t;
struct A { int i; };
struct B : A { int j; };
typedef boost::shared_ptr<A> APtr;
typedef boost::shared_ptr<B> BPtr;
result_t f1(APtr) { return 1; }
result_t f2(BPtr) { return 2; }
typedef boost::function <result_t(APtr const&)> funOfA;
typedef boost::function <result_t(BPtr const&)> funOfB;
struct Wrapper {
typedef result_t result_type;
funOfB _f;
result_t operator()(APtr const& a) {
return _f(boost::static_pointer_cast<B>(a));
}
};
funOfA wrapFunOfB(const funOfB f) {
Wrapper wrap = { f };
return wrap;
}
void F(const funOfA f) {
APtr a = boost::make_shared<A>();
APtr b = boost::make_shared<B>();
//std::cout << "f(a): " << f(a) << "\n"; // UNDEFINED BEHAVIOUR if f wraps a funOfB
std::cout << "f(b): " << f(b) << "\n";
}
int main() {
F(f1);
F(wrapFunOfB(f2));
}
Prints
f(b): 1
f(b): 2
PROBLEMS, WARNINGS: dynamic_pointer_cast<>
If F
actually invokes the parameter on an object that isn't actually of type B
, that static_cast<>
will invoke Undefined Behaviour.
If you want to protect against that, use dynamic_pointer_cast
, which requires the classes A
and B
to be polymorphic types.
Live On Coliru
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/function.hpp>
#include <iostream>
typedef int result_t;
struct A { int i; virtual ~A() {} };
struct B : A { int j; };
typedef boost::shared_ptr<A> APtr;
typedef boost::shared_ptr<B> BPtr;
result_t f1(APtr a) { return a?1 : 0; }
result_t f2(BPtr b) { return b?2 : -99; }
typedef boost::function <result_t(APtr const&)> funOfA;
typedef boost::function <result_t(BPtr const&)> funOfB;
struct Wrapper {
typedef result_t result_type;
funOfB _f;
result_t operator()(APtr const& a) {
return _f(boost::dynamic_pointer_cast<B>(a));
}
};
funOfA wrapFunOfB(const funOfB f) {
Wrapper wrap = { f };
return wrap;
}
void F(const funOfA f) {
APtr a = boost::make_shared<A>();
APtr b = boost::make_shared<B>();
std::cout << "f(a): " << f(a) << "\n";
std::cout << "f(b): " << f(b) << "\n";
}
int main() {
F(f1);
F(wrapFunOfB(f2));
}
Prints
f(a): 1
f(b): 1
f(a): -99
f(b): 2
C++11 Version
Things get a little more elegant here. Notably, the Wrapper class can be local, and anonymous:
funOfA wrapFunOfB(const funOfB f) {
struct {
typedef result_t result_type;
funOfB _f;
result_t operator()(APtr const& a) {
return _f(std::dynamic_pointer_cast<B>(a));
}
} wrap { f };
return wrap;
}
Next level: use a lambda instead:
funOfA wrapFunOfB(const funOfB f) {
return [f](APtr const& a) { return f(std::dynamic_pointer_cast<B>(a)); };
}
Live On Coliru
#include <memory>
#include <functional>
#include <iostream>
typedef int result_t;
struct A { int i; virtual ~A() {} };
struct B : A { int j; };
typedef std::shared_ptr<A> APtr;
typedef std::shared_ptr<B> BPtr;
result_t f1(APtr a) { return a?1 : 0; }
result_t f2(BPtr b) { return b?2 : -99; }
typedef std::function<result_t(APtr const&)> funOfA;
typedef std::function<result_t(BPtr const&)> funOfB;
funOfA wrapFunOfB(const funOfB f) {
return [f](APtr const& a) { return f(std::dynamic_pointer_cast<B>(a)); };
}
void F(const funOfA f) {
APtr a = std::make_shared<A>();
APtr b = std::make_shared<B>();
std::cout << "f(a): " << f(a) << "\n";
std::cout << "f(b): " << f(b) << "\n";
}
int main() {
F(f1);
F(wrapFunOfB(f2));
}
That's 25% code reduction.
来源:https://stackoverflow.com/questions/49325968/using-boostfunction-with-a-parameter-to-shared-pointer-to-derived-class