views:

137

answers:

5

Let have a type T and a struct having ONLY uniform elements of T type.

struct Foo {
    T one,
    T two,
    T three
};

I'd like to access them in fallowing way:

struct Foo {
    T one,
    T two,
    T three

    T &operator [] (int i)
    {
        return *(T*)((size_t)this + i * cpp_offsetof(Foo, two));
    }
};

where cpp_offsetof macro (it is considered to be correct) is:

#define cpp_offsetof(s, m)   (((size_t)&reinterpret_cast<const volatile char&>((((s*)(char*)8)->m))) - 8)

The C++ standard doesn't guarantee it, but can we assume that members are distanced by a fixed offset and above is correct, cross-platform solution?


100% compatible solution would be:

struct Foo {
    T one,
    T two,
    T three

    T &operator [] (int i) {
        const size_t offsets[] = { cpp_offsetof(Foo, one), cpp_offsetof(Foo, two), cpp_offsetof(Foo, three) };
        return *(T*)((size_t)this + offsets[i]);
    }
};

[edit]standard, compliant and faster version was presented by snk_kid using pointers to data members[/edit]
but it requires extra lookup table which I'm trying to avoid.

//EDIT
And one more. I cannot use just an array and constants to index these fields, they have to be named fields of a struct (some macro requires that).

//EDIT2
Why those have to be named fields of a struct? What is the macro? It is settings system of a bigger project. Simplifying it's sth like this:

struct Foo {
    int one;
    int two;
}
foo;

struct Setting { void *obj, size_t filed_offset, const char *name, FieldType type }

#define SETTING(CLASS, OBJ, FIELD, TYPE) { OBJ, cpp_offsetof(CLASS, FIELD), #OBJ #FIELD, TYPE }

Setting settings[] = {
    SETTING(Foo, foo, one, INT_FIELD),
    SETTING(Foo, foo, two, INT_FIELD)
};

And once again: I'm not looking form 100% compatible solution but 99%. I'm asking if we can expect that some compilers will put non-uniform padding between uniform fields.

A: 

You can't because the compiler can add dead bytes between members to allow padding.

There is two ways to do what you want.

The first is to use your compiler-specific keyword or pragma macro that will force the compiler to not add padding bytes. But that is not portable. That said it might be the easiest way to do it with your macro requirements, so I suggest you explore this possibility and prepare for adding more pragma when using different compilers.

The other way is to first make sure your members are aligned, then add accessors :

struct Foo {

   T members[ 3 ]; // arrays are guarrantied to be contigu


   T& one() { return members[0]; } 
   const T& one() const { return members[0]; } 
   //etc... 

};
Klaim
Firstly I'm asking if we can assume that padding (if added) will be fixed for each field. Secondly I'm asking for cross-platform solution. Thirdly a have to have named fields, not methods.
adf88
Then it's impossible in C++ AFAIK
Klaim
Your last requirement kill any other possiblity. Maybe you can add more macros to make your class work with your macros, but that's really bad style and evil.
Klaim
Programming efficient code in C++ is as much knowing what the target compilers promise as what std C++ promises, of course....
Simon Buchan
How a macro could help here?
adf88
Klaim is correct. Your requirements are more than a little bad- it's no wonder that you can't get a cross-platform solution.
DeadMG
Simon> Agreed.adf88> I don't know what your macros does, it depends on it. I'm adding details about the first answer.
Klaim
I explained that, see my EDIT2
adf88
+5  A: 

Your code doesn't work with NON-POD types such those which using virtual member functions. There is a standard compliant (and efficient) way to achieve what you're trying to do, using pointer to data members:

template< typename T >
struct Foo {

    typedef size_t size_type;

private:

    typedef T Foo<T>::* const vec[3];

    static const vec v;

public:

    T one;
    T two;
    T three;

    const T& operator[](size_type i) const {
        return this->*v[i];
    }

    T& operator[](size_type i) {
        return this->*v[i];
    }
};

template< typename T >
const typename Foo<T>::vec Foo<T>::v = { &Foo<T>::one, &Foo<T>::two, &Foo<T>::three };

Just make sure you use const every with the table of pointer to data-members to get optimizations. Check here to see what I'm talking about.

snk_kid
Why did I get rated down, when I've given a standard compliant solution?
snk_kid
-1 from me too, why did you wrote the same as me in my question?
adf88
@adf88: How is what snk_kid wrote the same as what's in your question?
Charles Bailey
@adf88 what the hell are you talking about?
snk_kid
@adf88: WTF, it's not even remotely similar to what you did in your question.
DeadMG
OK, ALLMOST the same. I wrote "it requires extra lookup table which I'm trying to avoid."
adf88
I was about to say this is no more compliant than `((T*)one)[i]`, then I saw it was a `array of four ((pointers to T) members of Foo)`, not `a (pointer to (array of four T)) members of Foo`. Stupid C typedefs.
Simon Buchan
@Simon Buchan I made a copy-paste mistake there, it should be 3 not 4. It's a constant static array of pointer to data members of T.
snk_kid
@snk_kid: Sorry, I'm just saying I *still* have trouble reading C++ typedefs, even when I've written plenty of them.
Simon Buchan
There is an overhead. http://pastebin.com/FhgLtv4x, there is a piece of code and disassembly from GCC (-o3), instruction main+7 is a table lookup.
adf88
@adf88: it doesn't get rid of the lookup table - it makes the lookup table language compliant (so it'll work under all compilers, platforms, architectures, optimizations, etc. etc). The same can't be said for your implementation.
Michael Burr
Yes, I agree. My implementation was just to present what I'm trying to avoid.
adf88
+1  A: 

Another way is with template specialization if what you are trying to achieve is still a compile time feature.

class Foo {
    T one;
    T two;
    T three; 
};

template <int i> T & get(Foo& foo);

template T& get<1>(Foo& foo){ return foo.one;}
template T& get<2>(Foo& foo){ return foo.two;}
template T& get<3>(Foo& foo){ return foo.three;}

It would be nice to define get as a member function but you cannot specialize template member functions. Now if this is only a compile time expansion you are looking for then this will avoid the lookup table issue of one of the previous posts. If you need runtime resolution then you need a lookup table obviously.

-- Brad Phelan http://xtargets.heroku.com

bradgonesurfing
Brad, I went in and made the code compilable, I hop that's Ok. If I blew it, feel free to roll back.
sbi
Also, `class Foo { template<int i> get() {return ::get<i>(*this);} };` should do the member function thing.
sbi
no problem with you editing it. always hard to write c++ correct without a compiler to spell check for you!
bradgonesurfing
A: 

You might be able to achieve what you want using an array to hold the data (so you can get indexed access without using a lookup table) and having references to the various array elements (so you can have 'named' elements for use by your macros).

I'm not sure what your macros require, so I'm not 100% sure this will work, but it might. Also, I'm not sure that the slight overhead of the lookup table approach is worth jumping through too many hoops to avoid. On the other hand, I don't think the approach I suggest here is any more complex than the table-of-pointers approach, so here it is for your consideration:

#include <stdio.h>

template< typename T >
struct Foo {

private:    
    T data_[3];

public:

    T& one;
    T& two;
    T& three;

    const T& operator[](size_t i) const {
        return data_[i];
    }

    T& operator[](size_t i) {
        return data_[i];
    }

    Foo() :
        one( data_[0]),
        two( data_[1]),
        three( data_[2])
        {};

};


int main()
{
    Foo<int> foo;

    foo[0] = 11;
    foo[1] = 22;
    foo[2] = 33;

    printf( "%d, %d, %d\n", foo.one, foo.two, foo.three);

    Foo<int> const cfoo( foo);

    printf( "%d, %d, %d\n", cfoo[0], cfoo[1], cfoo[2]);

    return 0;
}
Michael Burr
A: 

If you're sure the compilers you're using are going to generate the right code for this (and I'd imagine they would, assuming T isn't a reference type anyway) the best thing to do is put in some kind of check that the struct is laid out as you think. I can't think of any particular reason to insert non-uniform padding between adjacent members of the same type, but if you check the struct layout by hand then you'll at least know if it happens.

If the struct (S) has exactly N members of type T, for example, you can check at compile time that they are tightly packed simply using sizeof:

struct S {
    T a,b,c;
};

extern const char check_S_size[sizeof(S)==3*sizeof(T)?1:-1];

If this compiles, then they're tightly packed, as there's no space for anything else.

If you just happen to have N members, that you want to ensure are placed directly one after the other, you can do something similar using offsetof:

class S {
    char x;
    T a,b,c;
};

extern const char check_b_offset[offsetof(S,b)==offsetof(S,a)+sizeof(T)?1:-1];
extern const char check_c_offset[offsetof(S,c)==offsetof(S,b)+sizeof(T)?1:-1];

Depending on the compiler, this might have to become a runtime check, possibly not using offsetof -- which you might want to do for non-POD types anyway, because offsetof isn't defined for them.

S tmp;
assert(&tmp.b==&tmp.a+1);
assert(&tmp.c==&tmp.b+1);

This doesn't say anything about what to do if the asserts start failing, but you should at least get some warning that the assumptions aren't true...

(By the way, insert appropriate casts to char references and so on where appropriate. I left them out for brevity.)

brone