Accessing the attributes of a struct in C++ as array elements?

Accessing the attributes of a struct in C++ as array elements?

In C++, it might be reasonable to represent a URL using a class or a struct made of several strings, like so:

struct basic {
    std::string protocol;
    std::string username;
    std::string password;
    std::string hostname;
    std::string port;
    std::string pathname;
    std::string search;
    std::string hash;
};        

To associate each component with an index, we can use an enum class:

enum class component {
     PROTOCOL = 0,
     USERNAME = 1,
     PASSWORD = 2,
     HOSTNAME = 3,
     PORT = 4,
     PATHNAME  = 5,
     SEARCH = 6,
     HASH = 7,
};        

Initially, you might use a switch statement to access components:

std::string& get_component(basic& url, component comp) {
    switch (comp) {
        case component::PROTOCOL: return url.protocol;
        case component::USERNAME: return url.username;
        case component::PASSWORD: return url.password;
        case component::HOSTNAME: return url.hostname;
        case component::PORT:     return url.port;
        case component::PATHNAME: return url.pathname;
        case component::SEARCH:   return url.search;
        case component::HASH:     return url.hash;
    }
}        

The switch case might introduce performance overhead if accessed frequently.

For better performance, you could store the components in an array:

struct fat {
    std::array<std::string, 8> data;
    std::string &protocol = data[0];
    std::string &username = data[1];
    std::string &password = data[2];
    std::string &hostname  = data[3];
    std::string &port  = data[4];
    std::string &pathname = data[5];
    std::string &search = data[6];
    std::string &hash = data[7];
};        

Alternatively, you could avoid hardcoding the indexes:

struct fat {
    std::array<std::string, 8> data;
    std::string& protocol  = data[int(Component::PROTOCOL)];
    std::string& username  = data[int(Component::USERNAME)];
    std::string& password  = data[int(Component::PASSWORD)];
    std::string& hostname  = data[int(Component::HOSTNAME)];
    std::string& port      = data[int(Component::PORT)];
    std::string& pathname  = data[int(Component::PATHNAME)];
    std::string& search    = data[int(Component::SEARCH)];
    std::string& hash      = data[int(Component::HASH)];
};        

With this new data structure, getting a component by its index becomes simpler:

std::string& get_component(fat& url, component comp) {
    return url.data[int(comp)];
};        

Unfortunately, each reference in the new fat data structure might use 8 bytes on a 64-bit system. That is not a concern if you expect to have few instances of the data structures. However, if you do repeated create instances, you might want to avoid the references. You might try to replace the references by simple methods:

struct advanced {
    std::array<std::string, 8> data;
    std::string &protocol() { return data[0]; }
    std::string &username() { return data[1]; }
    std::string &password() { return data[2]; }
    std::string &hostname() { return data[3]; }
    std::string &port() { return data[4]; }
    std::string &pathname() { return data[5]; }
    std::string &search() { return data[6]; }
    std::string &hash() { return data[7]; }
};        

Or, if you prefer, you can be more explicit:

struct fat_advanced {
    std::array<std::string, 8> data;
    
    std::string& protocol()  { return data[int(Component::PROTOCOL)]; }
    std::string& username()  { return data[int(Component::USERNAME)]; }
    std::string& password()  { return data[int(Component::PASSWORD)]; }
    std::string& hostname()  { return data[int(Component::HOSTNAME)]; }
    std::string& port()      { return data[int(Component::PORT)]; }
    std::string& pathname()  { return data[int(Component::PATHNAME)]; }
    std::string& search()    { return data[int(Component::SEARCH)]; }
    std::string& hash()      { return data[int(Component::HASH)]; }
};        

It is not entirely satisfactory as it requires calling methods instead of accessing attributes. Eugene Ostroukhov has a better approach using pointer-to-member operator. We create a compile-time array with pointers to the attribute of the ‘basic’ struct:

std::string& get_component_fast(basic& url, component comp) {
    constexpr static std::array accessors = {&basic::protocol, 
      &basic::username, &basic::password, &basic::hostname, 
      &basic::port, &basic::port, &basic::pathname, 
      &basic::search, &basic::hash};
    return url.*accessors[int(comp)];
}
        

In this manner, you do not need to modify the original class, and you are likely to get excellent performance.


Reference: https://lemire.me/blog/2024/12/16/accessing-the-attributes-of-a-struct-in-c-as-array-elements/

Caio Licínio Vasconcelos Pantarotto

MSc Physics | Data Scientist | Machine Learning | Software Engineer

8mo

Is she checking for buffer overflows or if the struct fits in the cache ?

Like
Reply
Ludovic Pénet

Director of Engineering at Dataiku

8mo

Some introspection capability is missing to C++ for so long...

Like
Reply
laaziz lahlou

Post-Doctoral Researcher a ÉTS - École de technologie supérieure

8mo

It is always a pleasure to read your pedagogical posts full of technical wisdom.

Like
Reply

To view or add a comment, sign in

Others also viewed

Explore topics