12

The following reduced code sample does not do anything useful but two subsequent assignments to a data member pointer. The first assignment works, the second one gives a compiler error. Presumably because its to a nested member.

Question would be: Is it really just not possible to let a member pointer point to a nested member or am I missing any fancy syntax there?

struct Color {
    float Red;
    float Green;
    float Blue; };


struct Material {
    float Brightness;
    Color DiffuseColor; };


int main() {
    float Material::* ParamToAnimate;
    ParamToAnimate = &Material::Brightness;       // Ok
    ParamToAnimate = &Material::DiffuseColor.Red; // Error! *whimper*
    return 0; }

ATM I am working around by using byte offsets and a lot of casts. But that is ugly, I would better like to use those member pointers.

Yes, I know that question surely arised before (like nearly any question). Yes, I searched beforehand but found no satisfying answer.

Thanks for your time.

user2672165
  • 2,986
  • 19
  • 27
Ole Dittmann
  • 1,764
  • 1
  • 14
  • 22
  • jpalecek is correct; the answer to your question is no. But what you're really looking for is a solution to a problem. With a minor restructure of your data, you _can_ find a type which points to all four floats. (See below.) – John McFarlane Jun 09 '11 at 06:53

8 Answers8

5

I assume you are trying to get the pointer to the datamember Red. Since this is defined in the struct Color the type of the pointer is Color::*. Hence your code should be:

int main() {
    float Color::* ParamToAnimate;
    ParamToAnimate = &Color::Red; 
    return 0; }

To use it, you need to bind it to an instance of Color for example:

void f(Color* p, float Color::* pParam)
{
    p->*pParam = 10.0;
}
int main() {
    float Color::* ParamToAnimate;
    ParamToAnimate = &Color::Red; 

    Material m;
    f(&m.DiffuseColor, ParamToAnimate);
    return 0;
}

EDIT: Is it not possible to make the animation function a template? For example:

template<class T>
void f(T* p, float T::* pParam)
{
    p->*pParam = 10.0;
}
int main() {

    Material m;

    f(&m.DiffuseColor, &Color::Red);
    f(&m, &Material::Brightness);
    return 0;
}
Naveen
  • 74,600
  • 47
  • 176
  • 233
  • This has a big problem, that you can't animate brightness with this architecture. – jpalecek Aug 09 '10 at 10:38
  • @jpalecek: Yes you are right. I was concentrating more on syntax. – Naveen Aug 09 '10 at 10:41
  • Uhm, yes, but using different pointers would render the whole thing pointless. I want a single pointer that stores which float in material (or its nested members) has to be animated. And in reality I have of course even more nested members there in material. Theoretically it should be possible. My solution with the byte offsets and a lot of casts works. Its just a syntax thing. – Ole Dittmann Aug 09 '10 at 10:46
  • And what would the stored member pointer now look like? I would still need several of them, right? – Ole Dittmann Aug 09 '10 at 11:16
  • The templated method doesn't work - he needs to be able to get the float from an instance of `Material`, regardless of whether the `float` is a member of `Material` or of `Color`. – JoeG Aug 09 '10 at 11:30
5

AFAIK, this is not possible. A pointer-to-member can only be formed by an expression of type &qualified_id, which is not your case.

Vite Falcon's solution is probably the most appropriate.

jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • I am also afraid it is just not possible. Maybe I have to stick with my byte offset solution. Using absolute float pointers would not be the same. – Ole Dittmann Aug 09 '10 at 11:31
  • Although I dislike the reference to Falcon, your Answer is probably the right one. Its not possible - sadly. – Ole Dittmann Sep 09 '10 at 11:27
3

Instead of a member pointer, you can use a functor that returns a float* when given an instance of Material; change the type of ParamToAnimate to something like:

std::function<float*(Material&)>

On the plus side, it's portable - but on the downside, it requires a significant amount of boilerplate code and has significant runtime overhead.

If this is performance critical, I'd be tempted to stick with the offset method.

JoeG
  • 12,994
  • 1
  • 38
  • 63
2

Basically you're trying to get a pointer to a float variable that you can animate. Why not use float*. The issue you're having there is that Brightness is a member of Material, however, Red is a member of Color and not Material, to the compiler. Using float* should solve your problem.

Vite Falcon
  • 6,575
  • 3
  • 30
  • 48
  • A simple float pointer would be an absolute pointer to a single memory location. It could not be used on several material objects and would become invalid if material changes its memory location. – Ole Dittmann Aug 09 '10 at 10:50
  • 2
    As far as I understand, a pointer will always be invalid if material changes it's memory location. No pointer follows the change in memory location. – Vite Falcon Aug 09 '10 at 11:02
  • 5
    Member pointers do follow memory locations! They are only relative offsets into an object. You must specify an additional instance to access them. – Ole Dittmann Aug 09 '10 at 11:18
  • Until the question is updated with an example that requires pointer-to-member, I'm inclined to go with this solution. – John McFarlane Aug 12 '22 at 06:47
2

It's not possible. But there is a workaround very close to what you want to achieve. It involves putting the nested member into an union alongside with a "layout-compatible" anonymous struct. The downside is a bit bloated interface and the need of keeping definitions of sibling structs in sync.

struct Color {
    float Red;
    float Green;
    float Blue; };

struct Material {
    float Brightness;
    union {
        struct { // "Layout-compatible" with 'Color' (see citation below)
            float DiffuseColorRed;
            float DiffuseColorGreen;
            float DiffuseColorBlue; };
        Color DiffuseColor; }; };

int main() {
    Material M;

    float Material::* ParamToAnimate;
    ParamToAnimate = &Material::DiffuseColorRed;
    std::cin >> M.*ParamToAnimate;
    std::cout << M.DiffuseColor.Red << std::endl;
    return 0; }

ISO IEC 14882-2003 (c++03):

§3.9

11

If two types T1 and T2 are the same type, then T1 and T2 are layout-compatible types. [Note: Layout-compatible enumerations are described in 7.2. Layout-compatible POD-structs and POD-unions are described in 9.2. ]

§9.2

16

If a POD-union contains two or more POD-structs that share a common initial sequence, and if the POD-union object currently contains one of these POD-structs, it is permitted to inspect the common initial part of any of them. Two POD-structs share a common initial sequence if corresponding members have layout-compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.

Multiple nesting is possible too:

struct Color {
    float Red;
    float Green;
    float Blue; };

struct Material {
    float Brightness;
    Color DiffuseColor; };

struct Wall {
    union {
        struct {
            float SurfaceBrightness;
            struct {
                float SurfaceDiffuseColorRed;
                float SurfaceDiffuseColorGreen;
                float SurfaceDiffuseColorBlue; }; };
        Material Surface; }; };

int main() {
    Wall W;

    float Wall::* ParamToAnimate;
    ParamToAnimate = &Wall::SurfaceDiffuseColorRed;
    std::cin >> W.*ParamToAnimate;
    std::cout << W.Surface.DiffuseColor.Red << std::endl;
    return 0; }

§9.2

14

Two POD-struct (clause 9) types are layout-compatible if they have the same number of nonstatic data members, and corresponding nonstatic data members (in order) have layout-compatible types (3.9).

anonymous
  • 21
  • 2
1

How about inheritance instead of composition?

struct Color {
    float Red;
    float Green;
    float Blue; };

struct DiffuseColor : public Color {
    };

struct Material : public DiffuseColor {
    float Brightness; };


int main() {
    float Material::* ParamToAnimate;
    ParamToAnimate = &Material::Brightness;       // Ok
    ParamToAnimate = &Material::DiffuseColor::Red; // Ok! *whew*
    return 0; }
John McFarlane
  • 5,528
  • 4
  • 34
  • 38
0

You could simply refactor such that you don't have the nested structure at all. Add a setter than unpacks the color into its component parts so that existing code need not change much, and go from there.

You could also take an optional second pointer that digs into the nested type. A single test to see if you need the second parameter may prove good enough compared to your current method, and would be more easily extended should additional fields turn up later.

Take that a step further, and you have a base MaterialPointer class with a virtual Dereference method. The case class can handle simple members, with derived classes handling nested members with whatever additional information they need to find them. A factory can then produce MaterialMember* objects of the appropriate type. Of course, now you're stuck with heap allocations, so this is likely a little too far to be practical.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38
  • All those are possible alternatives. But they also are more complicated and/or less performant than my existing solution with byte offsets and casts. – Ole Dittmann Aug 10 '10 at 12:12
0

Since at some point you need a pointer to the actual data, this may or may not work for you:

float Material::* ParamToAnimate;
ParamToAnimate = &Material::Brightness;       // Ok
float Color::* Param2;
Param2 = &Color::Red; 

Material mat;
mat.Brightness = 1.23f;
mat.DiffuseColor.Blue = 1.0f;
mat.DiffuseColor.Green = 2.0f;
mat.DiffuseColor.Red = 3.0f;

float f = mat.DiffuseColor.*Param2;
John Dibling
  • 99,718
  • 31
  • 186
  • 324