A continuation of Effective Implementation of C++ Properties
by Viktor Korsun
Gašper Ažman
x.foo = 5;
Assignment
int bar = x.foo;
Reading
If it quacks like a field, it's gotta be...
struct Airplane {
int cargo;
} airplane;
... a field, right?
Storage is optional
struct Airplane {
struct Cargo {
int operator=(int y) {
/* haha, ignore */
return 0;
}
operator int() const {
return 0;
}
};
Cargo cargo;
} airplane;
Real programmers don't use properties
It is bad design; methods do exist.
That's that Java thing, right?
I can fix my codebase with sed!
* or another of a million reasons they are handy, sometimes
You need properties when you want to interact with the host object
Otherwise, just use a type.
struct Airplane {
bool has_cargo;
struct Cargo {
int operator=(int y) {
has_cargo = rand()%2;
return 0;
}
operator int() const {
return has_cargo ? rand()%5 : 0;
}
};
Cargo cargo;
} airplane;
Problem: The above does not work.
not (Cargo*) this
struct Airplane {
bool has_cargo;
struct Cargo {
int operator=(int y) {
host->has_cargo = rand()%2;
return 0;
}
operator int() const {
return host->has_cargo ? rand()%5 : 0;
}
Airplane* host;
};
Cargo cargo;
// constructor
Airplane() : cargo{this} {}
} airplane;
We could put the offset in the type of the property!
Airplane |
---|
bool is_cargo; |
Cargo cargo; |
Fixed offset
(Airplane*) = (Cargo*) this - offset
... with a sprinkle of reinterpret casts to char*
struct Airplane {
bool has_cargo;
struct Cargo {
// ...
};
Cargo cargo;
} airplane;
static auto const cargo_offset = offsetof(Airplane, cargo);
Airplane* cargo_to_airplane(Cargo* cargo) {
auto const raw_cargo = reinterpret_cast<char*>(cargo);
auto const raw_airplane = raw_cargo - cargo_offset;
return reinterpret_cast<Airplane*>(raw_airplane);
}
struct Airplane {
bool has_cargo;
struct Cargo {
// ...
};
Cargo cargo;
} airplane;
static auto const cargo_offset = offsetof(Airplane, cargo);
static auto const cargo_offset;
struct Airplane {
bool has_cargo;
struct Cargo {
operator=(int x) {
cargo_to_airplane(this)->has_cargo = rand()%x;
}
};
Cargo cargo;
} airplane;
static auto const cargo_offset = offsetof(Airplane, cargo);
struct Airplane {
bool has_cargo;
auto set_cargo(int x) {
return has_cargo = rand()%x;
}
auto get_cargo() const {
return has_cargo;
}
PROPERTY(cargo, get_cargo, set_cargo);
};
But offsetof() does *not* work inside the class. It needs a complete type.
struct Airplane {
bool has_cargo;
struct Cargo {
operator=(int x) {
cargo_to_airplane(this)->has_cargo = rand()%x;
}
};
Cargo cargo;
// We can't call offsetof, but we *can* do this.
static auto constexpr cargo_address() const {
return &Airplane::cargo;
}
} airplane;
template <typename Host, typename Member>
static auto constexpr offset_of(Member* cargo) {
return reinterpret_cast<size_t>(&(Host*)0->*cargo));
}
Airplane* cargo_to_airplane(Cargo* cargo) {
auto const raw_cargo = reinterpret_cast<char*>(cargo);
auto const cargo_offset =
offset_of<Airplane>(Airplane::cargo_offset());
auto const raw_airplane = raw_cargo - cargo_offset;
return reinterpret_cast<Airplane*>(raw_airplane);
}
Contiguous code, not too much badness...
struct Airplane {
bool has_cargo;
struct Cargo {
operator=(int x) {
cargo_to_airplane(this)->has_cargo = rand()%x;
}
};
Cargo cargo;
Cargo more_cargo;
static auto constexpr cargo_address() const {
return &Airplane::cargo;
}
// ... THIS IS HORRIBLE.
static auto constexpr more_cargo_address() const {
return &Airplane::cargo;
}
} airplane;
Does not work.
There's a copy-paste bug on this slide. ->macro
template <typename T> type_ { using type = T; };
struct Airplane {
bool has_cargo;
template <typename Tag>
struct Cargo {
using tag = Tag;
operator=(int x) {
cargo_to_airplane<tag>(this)->has_cargo = rand()%x;
}
};
struct cargo_tag;
Cargo<cargo_tag> cargo;
static auto constexpr member_address(type_<cargo_tag>) const {
return &Airplane::cargo;
}
struct more_cargo_tag
Cargo<more_cargo_tag> more_cargo;
static auto constexpr member_address(type_<more_cargo_tag>) const {
return &Airplane::more_cargo;
}
} airplane;
template <typename Cargo>
Airplane* cargo_to_airplane(Cargo* cargo) {
auto const raw_cargo = reinterpret_cast<char*>(cargo);
auto const tag = type_<typename Cargo::tag>{};
auto const cargo_offset =
offset_of<Airplane>(Airplane::member_offset(tag)));
auto const raw_airplane = raw_cargo - cargo_offset;
return reinterpret_cast<Airplane*>(raw_airplane);
}
template <typename Host, typename Member>
static auto constexpr offset_of(Member* cargo) {
return reinterpret_cast<size_t>(&(Host*)0->*cargo));
}
... well, for the talk.
Talk to me later about how to
* make copying safe
* get rid of the one byte of overhead,
* make this a single-line macro.
Library's here:
https://github.com/bitekas/properties