Can I assign a member data pointer to a derived type?
-
14-11-2019 - |
Question
This is probably best shown with example code. The following fails to compile with g++:
struct Base {
};
struct Derived : public Base {
};
struct Container {
Derived data_;
};
int main(void) {
Base Container::*ptr = &Container::data_;
}
I get the following error: invalid conversion from 'Derived Container::*' to Base Container::*'
.
Is this not allowed by the language? Is this a compiler bug? Am I using the wrong syntax?
Please help!
Some background as to why I'm trying to do this: I have several member data pieces that I want to use primarily as their derived types, but I want to be able to populate them through some common code. Data will be coming in an arbitrary order and have a string label that I would use to select the appropriate member data to populate. I was planning on creating a std::map<std::string, Base Container::*>
to assign data to each member through a common interface. I'd like to avoid have a giant if else
construct to find the right member data.
Solution
This is not a compiler bug, you can't do that. (But you can assign a Base::* to a Derived::*).
I don't see any good reason for the limitation (excepted that to handle the case of multiple inheritance, that would complicate even more the representation of a member pointer).
OTHER TIPS
There are a lot of fairly complex, some not-well-explained, and a few flat wrong answers in this thread.
But the problem, it seems to me, is that there simply isn't a Base
member within Container
-- there is a Derived
member. You can't do this:
Base Container::*ptr = &Container::data_;
...for the same reason you can't do this:
int a;
long* pl = &a;
In the second example, the object isn't a long
, it's an int
. Similarly, in the first example the object isn't a Base
, it's a Derived
.
As a possibly tangential point, it seems to me like what you really want to do is have Base
be an abstract class, and have Container
have a Base*
rather than a Derived
member.
Pointers to members in C++ are not really pointers but more like offsets to given member and are specific to the type, so what you are trying to do is not really supported.
Here's a decent discussion here on Stackoverflow C++: Pointer to class data member.
You just need to write:
Base* ptr = &container.data_;
but container
has to be an instance of Container
, so you have to create one variable of that type somewhere.
You cannot convert C::*A to C::*B even if there is a conversion possible between A and B.
However, you can do this:
struct Base
{
virtual ~Base() {}
virtual void foo() { std::cout << "Base::foo()\n"; }
};
struct Derived : Base
{
void foo() { std::cout << "Derived::foo()\n"; }
};
struct Bar
{
Base* x;
Bar() : x(new Derived) {}
};
int main()
{
Bar b;
Base* Bar::*p = &Bar::x;
(b.*p)->foo();
}
You would have to static_cast
to do this conversion as seen in 5.3.9/9. This reason for this is that it acts as a static_cast
from parent object pointer to child object pointer would. In other words, putting a pointer to a derived member into a pointer-to-parent-member would allow you to possibly access a non-existent derived member from a parent object or pointer. If the standard allowed this automatically it would be easy to mess up and try to access a child member on a class that isn't of the appropriate child type (that contains said member).
Without more information it sounds like you need a different/better constructor/set interface in your Base
class rather than trying to use pointers-to-member here.
I think what you want is a 'container', ie a struct which just has pointers:
struct Container{
Base* derivedAdata_;
Base* derivedBdata_;
...
};
Now each of the members you know to be of a specific type (ie DerivedA, DerivedB etc) so you can down-cast them later.
But first you are receiving data (in arbitrary order), but with a string name, so you should have a map:
std::map<std::string, Base* Container::*>
And you must have already populated the map:
myMap["DerivedA"] = &Container::derivedAdata;
...
Now data arrives and you start populating the container:
instance.*(myMap[key]) = factory(key, data);
myMap[key]
picks the right member of the container and factory(key,data)
creates instances.
btw you could just have a map as your container anyway:std::map<std::string, Base*>
Regarding the original issue, you can do this using pointer to functions, instead of introducing base classes.
class Container {
public:
void set(std::string const& label, std::string const& value);
void setName(std::string const& value) { _name = value; }
void setAge(std::string const& age) {
_age = boost::lexical_cast<size_t>(age);
}
private:
std::string _name;
size_t _age;
};
How to implement set
then ?
// container.cpp
typedef void (Container::*SetterType)(std::string const&);
typedef std::map<std::string, SetterType> SettersMapType;
SettersMapType SettersMap =
boost::assign::map_list_of("name", &Container::setName)
("age", &Container::setAge);
void Container::set(std::string const& label, std::string const& value) {
SettersMapType::const_iterator it = SettersMap.find(label);
if (it == SettersMap.end()) { throw UnknownLabel(label); }
SetterType setter = it->second;
(this->*setter)(value);
}
struct Container {
Derived data_;
};
int main(void)
{
Base Container::*ptr = &Container::data_;
}
The first problem is that Container
doesn't have a member called ptr
Container container_object;
Base *ptr = container_object.data_;
Would work. Note that there needs to be a container object to create the data_ member and it would need to be made public.
The alternative would be for derived::data_ to be a static member.