Sign in to follow this  

Can one access one class field, by another class field?

This topic is 832 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Let's say there is a class foo that has a variable of another class, call it C. Is is possible to access C members by a field of foo?

EDIT: I forgot to mention that it should be without any additional memory or significant performance loss (like with using references - mem loss).

class C
{
  public:
  float t;
};

class foo
{
  public:
   C c;
   float x; //this should access c.t
} f;

float T=f.x; //returns foo.c.t 
f.x=5f;
 

The similar example was given by Alvaro, however it regards an access between two fields of one class:

struct vector3 {
    float x, y, z;

    float& operator[](int index) {
        assert(index >= 0 && index < 3);
        return this->*members[index];
    }

    float operator[](int index) const {
        assert(index >= 0 && index < 3);
        return this->*members[index];
    }

    static float vector3::* const members[3];
};

float vector3::* const vector3::members[3] = { &vector3::x, &vector3::y, &vector3::z};
Edited by Misery

Share this post


Link to post
Share on other sites

@Madhed I forgot to add that it shouldn't add any memory overhead like it is with using references. I am just curious - no special need for it. I am still learning C++.

Share this post


Link to post
Share on other sites

If your C class has the same layout as a single float, theoretically, you could use a union, although until quite recently you couldn't even add objects to unions (but I think that got resolved in one of the newer C++ versions).

 

However, an object having the same layout as a native float is quite unlikely, and definitely not portable across different C++ compilers.

The Vector case is already quite edge case, if not already outside well-defined behavior.

 

So my guess is, it's not possible. If it is possible (some genius C++ wizard may be able to find a way), I'd say it's something you definitely don't want to use.

 

 

C and C++ are incredible flexible, they can be pushed into almost any shape you like, due to their "the programmer knows what he/she is doing" philosophy. That does not mean you should do these weird tricks, unless you are in an obfuscated code contest smile.png

 

Share this post


Link to post
Share on other sites

Alvaro's suggestion, using pointer math to get to sequential items, is unsafe and not guaranteed to work in all cases, but in practice works practically everywhere.  The three items are in order sequentially, so getting the address of the first item then using pointer math to advance to the other items will work.

 


I forgot to add that it shouldn't add any memory overhead like it is with using references. I am just curious - no special need for it. I am still learning C++.

There are non-portable ways to do what you describe, to access members inside another structure.  They rely on a knowledge of the compiler and hardware so that you know the system will put the data in the correct place and in the correct format. 

 

A union is one method of doing it.  Be aware that the language standard specifically warns the code is not portable and can break across machines.

union MyUnion {
    sometype a;
    othertype b;
    thattype c;
};

The three values, a, b, and c, all occupy the same block of memory. There are several rules about using them, and it is not something you should be doing as a beginner.

 

One of the more common versions of this was networking internet address structure.  It was a union of a 32-bit value for one type, and a structure with four 8-bit values for the second type.  That way you could address it as a 32-bit single value, or as four 8-bit values like aa.bb.cc.dd that most people are familiar with.

 

Since the exact layout of values is different on machines, the four values needed to be mapped on a compiler-specific and machine-specific basis.  The three major forms are for some systems a.b.c.d, sometimes d.c.b.a, sometimes b.a.d.c. 

 

 

There are a few other methods of doing it, mostly using raw memory addresses rather than named objects. All of them are inherently unsafe across machines and require specific knowledge of the compiler and architecture you are working on.

Share this post


Link to post
Share on other sites
For the OP: no, what you want is impossible, and the closest you can get is Aardvajk's suggestions.

There's a couple [url=https://groups.google.com/a/isocpp.org/forum/?fromgroups=#!topic/std-proposals/n0QcD_K5dBY%5B226-250%5D]rowdy[/url] and [url=https://groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-proposals/u35GIuJECcQ]opinionated[/url] threads on the isocpp groups right now from people wanting to propose support for such a language construct, if you're interested in reading those.
 

If your C class has the same layout as a single float, theoretically, you could use a union[/url]

[url=http://blog.regehr.org/archives/959]No, you can't.[/url] (read the whole thing) You can only read from the union member you've most recently assigned to. If you assign to c.x then the c member of the union is the active member; thus reading from the f member technically results in undefined behavior.

And that's not just academic. Modern optimizing compilers can and will assume that you are properly following the language's aliasing rules. Again, read the linked article for real-world examples of how GCC or Clang will "miscompile" code that violates the rules.

Share this post


Link to post
Share on other sites

Alvaro's suggestion, using pointer math to get to sequential items, is unsafe and not guaranteed to work in all cases, but in practice works practically everywhere.  The three items are in order sequentially, so getting the address of the first item then using pointer math to advance to the other items will work.


You ninja'd my post so you didn't see the article I posted, but do please read it now that I've posted. smile.png

You cannot and most certainly should not do what you suggest. The undefined behavior can include "works most of the time" which then leads you to spread that hack all over your codebase, but you _will_ find weird cases where the code you assume is valid and safe is "mangled" in completely legal ways by the compiler.

Having had to track down and fix these exact sorts of issues in AAA codebase after upgrading compilers... trust me, just save yourself the trouble and adhere to the language's rules. Edited by SeanMiddleditch

Share this post


Link to post
Share on other sites

The undefined behavior can include "works most of the time" which then leads you to spread that hack all over your codebase, but you _will_ find weird cases where the code you assume is valid and safe is "mangled" in completely legal ways by the compiler.

Right, which is why it works on most major compilers and architectures, and has shipped in thousands of code bases.

 

The standard has fairly strict requirements on ordering of objects inside structures, including what can be (and what cannot be) reordered.

 

Both of those items are used widely.  

 

The union of structs is commonplace, and as mentioned in my post has been part of network programming since the 1970s for Internet sockets While officially you can only read the format you wrote or read a common initial sequence shared with a type you wrote, nearly 40 years of code relies on that behavior and it isn't changing any time soon.  There is an enormous body of code out there relying on it.  Unions to detect byte ordering are common for file system code and networking libraries, as are unions to pack bytes into more complex structures then read them as shorts, longs, long longs.  Unions with bitfields to more easily modify floats and doubles are not strictly legal but widely used for decades. 

 

For access by memory address, it is similarly another programming tidbit used for ages. It's had many uses, including to access objects where you don't have the name but do know the address of a neighbor.  The standard has several guarantees such as how each must be in order, and have a higher address than the last. In practice it is reliable within builds, but details about locations and offsets can vary between builds.

 

For those links, fun reading but full of errors.  One claims there are only two byte orderings, little endian and big endian.  They forget -- probably by ignorance of not seeing enough variations of hardware -- about middle endian.  Dating myself a little bit, I worked on a PDP11 back in college days which was middle endian. 

 

 

 

You are right enough to call out they are not portable and not standard --- but they work reliably within implementations and exist in many large and established bodies of code. When you see them (and eventually you WILL see them) it is important to understand what you are looking at.

Share this post


Link to post
Share on other sites

Reiterating one case in point. MSDN documentation for the internet address structure.  Lovely use of unions, copy/pate for those who don't want to click the link.

typedef struct in_addr {
  union {
    struct {
      u_char s_b1,s_b2,s_b3,s_b4;
    } S_un_b;
    struct {
      u_short s_w1,s_w2;
    } S_un_w;
    u_long S_addr;
  } S_un;
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;

Designed so you can manipulate it as a 32-bit value, two 16-bit values, or four 8-bit values.

 

Similar structures exist on all platforms, but with the structs in different orders based on actual memory layout.

Share this post


Link to post
Share on other sites
Sure, it's used in thousands of code bases. Like the Linux kernel. Which explicitly has to be compiled with -fno-strict-aliasing _because it breaks the freakin' rules_ and out-of-the-box modern optimizing compilers miscompile the Linux kernel. Womp. tongue.png

The in_addr ones can work reliably in very specific circumstances. For one, you don't actually mix use of the two members simultaneously in correct code, but more importantly, aliasing with `char` is explicitly allowed by the aliasing rules.

The problem with the OP's structure has nothing to do with struct layout semantics. It has to do with the compiler and the assumptions it's allowed to make about member access, the aliasing, the way the compiler's allowed to reorder code, and the way it can assume you're not breaking the rules.

Given the previous definition of the union A, the statement `{ a.c.x = 1; a.f = 2; y = a.c.x; }` is allowed to result in y being 1. Because legally, given the "as-if" rule of compiler optimization and the rule that C and float cannot alias, the write to a.f cannot have legally modified a member of a.c, so the compiler is completely free to reorder the code to be equivalent to `{ y = 1; a.f = 2; }` (and completely elide the write to a.c) if it so chooses.

It's undefined behavior. Undefined behavior can and often does include "it just works"... up until the point where it doesn't anymore. Which is the worst kind of undefined behavior of all. smile.png

Been there, hit the bugs, and have cleaned up their mess. Old code is what it is, but new code has no excuse. smile.png

There are some very strictly limited forms of aliasing through unions that are legal. Those affordances have been expanded in C99/C11 but NOT in any of version yet of C++.

Share this post


Link to post
Share on other sites
We've seen what we think are compiler bugs (GCC) when doing type punning (with -no-strict-aliasing) when using certain combinations of optimisation flags. My colleague reported them but I've not tracked what happened.

Even if it is supported and legal, writing code that is pushing the compiler to its limits of complexity, just to be able to refer to one variable with the name of another seems to me a bad trade-off in production code.

So one has to look past the "can it be done" questions and ask "is it worth it?" at some point in this process. Edited by Aardvajk

Share this post


Link to post
Share on other sites

This topic is 832 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this