13

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.

preynold
  • 141
  • 3
  • Maybe it's just your example, but in gerenal, you should not be implementing your own containers. – Björn Pollex May 16 '11 at 13:26
  • It's pretty unlikely this is a compiler bug. Did you try to compile the example you posted? In main(), "&Container::data_" is not actually an instance, you don't have an object for "Container". – Lucky Luke May 16 '11 at 13:26
  • 3
    General comment to would be answerers: please learn about member pointer before answering. – AProgrammer May 16 '11 at 13:28

9 Answers9

3

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).

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • I see a perfectly good reason for the limitation -- it's not a `Base`! – John Dibling May 16 '11 at 13:56
  • @John, you can assign a Derived* to a Base* but not a Derived Foo::* to a Base Foo::*. I don't see any reason other than implementation complexity. (I see good reasons not to allow to assign a Foo Derived::* to a Foo Base::*. And you can assign a Foo Base::* to a Foo Derived::*). – AProgrammer May 16 '11 at 14:04
1

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.

Community
  • 1
  • 1
Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
1

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.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • I don't see how your `int` vs. `long` analogy holds; by that reasoning, wouldn't that disallow `Base* b = &derived` too? – jamesdlin May 23 '13 at 09:26
0

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.

Simone
  • 11,655
  • 1
  • 30
  • 43
0

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();
}
Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
0

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.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • Could you elaborate (possibly with an example) how automatically converting a `Derived Container::*` member to `Base Container::*` member could be unsafe? – jamesdlin May 23 '13 at 09:30
0

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*>

quamrana
  • 37,849
  • 12
  • 53
  • 71
0

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);
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
-1
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.

DanS
  • 1,677
  • 20
  • 30
  • You're completely missing the point. This question is asking about pointer-to-members. See http://stackoverflow.com/questions/670734/c-pointer-to-class-data-member – jamesdlin May 23 '13 at 09:15