Nested data member pointer - not possible?

被刻印的时光 ゝ 提交于 2019-12-29 07:31:11

问题


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.


回答1:


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.




回答2:


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;
}



回答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.




回答4:


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.




回答5:


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




回答6:


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.




回答7:


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;



回答8:


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; }


来源:https://stackoverflow.com/questions/3439074/nested-data-member-pointer-not-possible

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!