Heres a way to use a proxy class to access elements in a member array by name. It is very C++, and has no benefit vs. ref-returning accessor functions, except for syntactic preference. This overloads the ->
operator to access elements as members, so to be acceptable, one needs to both dislike the syntax of accessors (d.a() = 5;
), as well as tolerate using ->
with a non-pointer object. I expect this might also confuse readers not familiar with the code, so this might be more of a neat trick than something you want to put into production.
The Data
struct in this code also includes overloads for the subscript operator, to access indexed elements inside its ar
array member, as well as begin
and end
functions, for iteration. Also, all of these are overloaded with non-const and const versions, which I felt needed to be included for completeness.
When Data
's ->
is used to access an element by name (like this: my_data->b = 5;
), a Proxy
object is returned. Then, because this Proxy
rvalue is not a pointer, its own ->
operator is auto-chain-called, which returns a pointer to itself. This way, the Proxy
object is instantiated and remains valid during evaluation of the initial expression.
Contruction of a Proxy
object populates its 3 reference members a
, b
and c
according to a pointer passed in the constructor, which is assumed to point to a buffer containing at least 3 values whose type is given as the template parameter T
. So instead of using named references which are members of the Data
class, this saves memory by populating the references at the point of access (but unfortunately, using ->
and not the .
operator).
In order to test how well the compiler's optimizer eliminates all of the indirection introduced by the use of Proxy
, the code below includes 2 versions of main()
. The #if 1
version uses the ->
and []
operators, and the #if 0
version performs the equivalent set of procedures, but only by directly accessing Data::ar
.
The Nci()
function generates runtime integer values for initializing array elements, which prevents the optimizer from just plugging constant values directly into each std::cout
<<
call.
For gcc 6.2, using -O3, both versions of main()
generate the same assembly (toggle between #if 1
and #if 0
before the first main()
to compare): https://godbolt.org/g/QqRWZb
#include <iostream>
#include <ctime>
template <typename T>
class Proxy {
public:
T &a, &b, &c;
Proxy(T* par) : a(par[0]), b(par[1]), c(par[2]) {}
Proxy* operator -> () { return this; }
};
struct Data {
int ar[3];
template <typename I> int& operator [] (I idx) { return ar[idx]; }
template <typename I> const int& operator [] (I idx) const { return ar[idx]; }
Proxy<int> operator -> () { return Proxy<int>(ar); }
Proxy<const int> operator -> () const { return Proxy<const int>(ar); }
int* begin() { return ar; }
const int* begin() const { return ar; }
int* end() { return ar + sizeof(ar)/sizeof(int); }
const int* end() const { return ar + sizeof(ar)/sizeof(int); }
};
// Nci returns an unpredictible int
inline int Nci() {
static auto t = std::time(nullptr) / 100 * 100;
return static_cast<int>(t++ % 1000);
}
#if 1
int main() {
Data d = {Nci(), Nci(), Nci()};
for(auto v : d) { std::cout << v << ' '; }
std::cout << "\n";
std::cout << d->b << "\n";
d->b = -5;
std::cout << d[1] << "\n";
std::cout << "\n";
const Data cd = {Nci(), Nci(), Nci()};
for(auto v : cd) { std::cout << v << ' '; }
std::cout << "\n";
std::cout << cd->c << "\n";
//cd->c = -5; // error: assignment of read-only location
std::cout << cd[2] << "\n";
}
#else
int main() {
Data d = {Nci(), Nci(), Nci()};
for(auto v : d.ar) { std::cout << v << ' '; }
std::cout << "\n";
std::cout << d.ar[1] << "\n";
d->b = -5;
std::cout << d.ar[1] << "\n";
std::cout << "\n";
const Data cd = {Nci(), Nci(), Nci()};
for(auto v : cd.ar) { std::cout << v << ' '; }
std::cout << "\n";
std::cout << cd.ar[2] << "\n";
//cd.ar[2] = -5;
std::cout << cd.ar[2] << "\n";
}
#endif