7

I'm searching a way to get the offset of a member variable to statically pass this offset to the member variable. Basically I want achieve this:

template <std::intptr_t OFFSET>
struct A
{
    std::intptr_t self()
    {
        return reinterpret_cast<std::intptr_t>(this) - OFFSET;
    }
};
struct B
{
    int some_variables[256];
    A<???> a;
};
int main()
{
    B b;
    assert(reinterpret_cast<std::intptr_t>(&b) == b.a.self()); // shall pass
    return 0;
}

Is there a way to do this?

JulianW
  • 897
  • 9
  • 23
  • If `B` is a [standard-layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout) type, you have [`std::offsetof`](https://en.cppreference.com/w/cpp/types/offsetof). But is it? – YSC Feb 18 '19 at 12:24
  • 1
    @YSC `offsetof` is a macro. There is no `std::offsetof` – Indiana Kernick Feb 18 '19 at 12:26
  • 2
    @Kerndog73 Yes :D My fingers are, I guess, too used to type std:: – YSC Feb 18 '19 at 12:28
  • `offsetof` is not working: `error: invalid use of incomplete type ‘struct B’ A a;` – JulianW Feb 18 '19 at 12:28
  • @JulianH Declare B before you use B – Indiana Kernick Feb 18 '19 at 12:29
  • Ho yes, `offsetof(type, name)` needs `type` to be complete. You'd need to trick a bit. – YSC Feb 18 '19 at 12:29
  • @YSC this is because you do not want to do `using namespace std;`, me I use it and my fingers never write `std::` by themselves (take all of that as a joke please ^^) – bruno Feb 18 '19 at 12:30
  • Because I don't know the "trick" I asked here... :) – JulianW Feb 18 '19 at 12:30
  • @JulianH I don’t think it’s possible to get the offset of a member before that member has even been declared. Why do you need to do this anyway? What’s your real use case? I mean, it’s really quite a strange thing to do. – Indiana Kernick Feb 18 '19 at 12:34
  • Syntactically sugar. I'm saving the address of an object in the `this` pointer and then I want to be able to add functionallity by composing objects. I already have a solution to this problem by using inheritance, CRT pattern and `static_cast` the `this` pointer. But then I have the ugly syntax `b.A::something()` or I need to alias the function using `using` – JulianW Feb 18 '19 at 12:37
  • What’s wrong with CRTP? If B inherits A and A takes B as a template parameter then you can do b.something() just fine. A can static_cast the this pointer to access B and everything works fine. That’s how CRTP is usually done. What’s wrong with that syntax? – Indiana Kernick Feb 18 '19 at 12:41
  • the problem is, when I inherite more than one function with the same name, then I have to use the ugly syntax `b.A::something()`. – JulianW Feb 18 '19 at 12:45
  • Using composition was only an idea, if you have a better one to avoid the ugly syntax, I would be happy if you share it. – JulianW Feb 18 '19 at 12:46
  • @JulianH Then just give different functions different names – Indiana Kernick Feb 18 '19 at 12:48
  • @Kerndog73 This does not work, because my different base classes are the same class with different template arguments. And I don't want write for each possible case a new class, because the amount of classes is theoretically infinity. – JulianW Feb 18 '19 at 12:51
  • Duplicate of https://stackoverflow.com/questions/19943194/determining-struct-member-byte-offsets-at-compile-time – L. Scott Johnson Feb 18 '19 at 12:51
  • @JulianH The weird offset hack is much, much uglier than “the ugly syntax”. If you’re getting name collisions then just give stuff better names. – Indiana Kernick Feb 18 '19 at 12:52
  • @Kerndog73 yes, you are right, as I started I didn't see all the implications. – JulianW Feb 18 '19 at 13:17

2 Answers2

5

First of, as requested, you're goal is not achievable as the type of a impacts the offst of a inside B:

struct B
{
    int some_variables[256];
    A</* offset of a inside B */> a;
};

This is alignment.


You could use the standard macro offsetof. This implies two things:

  1. Since offsetof(type, member) is well-defined only for standard-layout types, the enclosing type must be standard-layout,
  2. and since offsetof can only be "called" on complete types, its statically-computed result can only be set to the subobject dynamically; it canoot be a template non-type parameter, but can be a constructor argument.

Full program

#include <cassert>
#include <cstdint>
#include <cstddef>

struct Location
{
    Location(std::size_t offset) : offset_(offset) {}
    std::size_t offset_;
    operator std::intptr_t () const { return reinterpret_cast<std::intptr_t>(this) - offset_; }
};

struct SomeType
{
    int some_variables[256];
    Location location = offsetof(SomeType, location);
};

int main()
{
    SomeType obj;
    assert(reinterpret_cast<std::intptr_t>(&obj) == obj.location); // does pass
}

live demo

But as you commented, this is quite useless as Location could be simply defined as

template<class T>
struct Location
{
    Location(T* location) : location_(location) {}
    T* location_;
    operator T* () const { return location; }
};

and initialized with Location location = this;.

Community
  • 1
  • 1
YSC
  • 38,212
  • 9
  • 96
  • 149
  • The answer didn't answer the question. The OP wants to statically know (not dynamically know) the offset, so that that offset can be used in the template. – L. Scott Johnson Feb 18 '19 at 12:48
  • Yeah, but your answer seems to indicate that it is dynamic rather than the requested compile-time. So an casual observer might be driven to downvote. – L. Scott Johnson Feb 18 '19 at 12:50
  • 1
    @L.ScottJohnson its even mentioned explicitly. Maybe a casual observer that didnt even read the anwer... – 463035818_is_not_an_ai Feb 18 '19 at 12:51
  • I've edited my answer to explain what's happening to that casual reader/pro-downvoter. Thank you @L.ScottJohnson – YSC Feb 18 '19 at 12:53
  • What you are doing is superfluous, because then I don't need offset of and simply use this: `Location location{this}; but I exactly don't want waste memory on saving the this pointer. – JulianW Feb 18 '19 at 12:53
  • @JulianH alas, you cannot. The type of `a` in your `B` influences where the `a` object is inside `B`. This is alignment. You cannot have the type of `a` depend on its offset. – YSC Feb 18 '19 at 12:56
  • @JulianH answer fixed. Is it an XY problem? – YSC Feb 18 '19 at 13:02
  • @YSC Maybe, but imo. my code implies that I don't want to save the this pointer ("get the offset of a member variable to statically pass this offset to the member variable"). Your code is passing the pointer at compile time. – JulianW Feb 18 '19 at 13:11
  • 1
    @YSC ok thank you, with your current answer I see there is a paradoxon in what I'm trying. So I think I will either stuck with inheritance or I will save the pointer. Thank you very much for your answer. – JulianW Feb 18 '19 at 13:14
0

I had an idea and eventually found a way to statically obtain the address of the member:

template <class T, class R, R T::* MEMBER>
struct B
{
    std::intptr_t self()
    {
        // got this reinterpret_cast from https://stackoverflow.com/a/5617632/4043866
        std::intptr_t offset = reinterpret_cast<std::intptr_t>(&(reinterpret_cast<T const volatile*>(NULL)->*MEMBER));
        std::intptr_t _this = reinterpret_cast<std::intptr_t>(this);
        return _this - offset - sizeof(R);
    }
};
template <class T, class... BASES>
constexpr std::size_t acc_sizes()
{
    return sizeof(T) + acc_sizes<BASES...>();
}
template <class... BASES>
struct B_START
{
    std::intptr_t self()
    {
        return reinterpret_cast<std::intptr_t>(this) - acc_sizes<BASES...>();
    }
};
struct Z
{
    int a;
};
#pragma pack(1)
struct A
    : Z
{
    B_START<Z> a;
    B<A, B_START<Z>, &A::a> b;
    B<A, B<A, B_START<Z>, &A::a>, &A::b> c;
};

Now I can write:

A a;
a.b.self();

To address the correct function, without any lose of memory space.

JulianW
  • 897
  • 9
  • 23