简体   繁体   中英

Accessing members of base classes in the derived class through runtime indexing

Consider the following code

#include <array>
#include <iostream>

template <std::size_t> struct base {
    std::size_t value;
};

struct derived: base<0>, base<1> {
    using pointer_type = std::size_t derived::*;
    static constexpr std::array<pointer_type, 2> members{{
        &derived::base<0>::value, 
        &derived::base<1>::value
    }};
    constexpr std::size_t& operator[](std::size_t i) noexcept {
        return this->*(members[i]);
    }
    constexpr const std::size_t& operator[](std::size_t i) const noexcept {
        return this->*(members[i]);
    }
};

int main(int, char**) {
    derived x{42, 84};
    std::cout << sizeof(base<0>) + sizeof(base<1>) << " " << sizeof(derived);
    std::cout << std::endl;
    std::cout << x[0] << " " << x[1];
    std::cout << std::endl;
    return 0;
}

It creates a templated structure base with a data member value , and a structure derived that inherits from several specializations of it. I would like to access the value of one or the other base class depending on an index provided at runtime. The provided code does not seem to achieve this, and always returns the value of the first base .

I would like to achieve it:

  • Without changing the code of base
  • Without changing the way derived inherits from the base
  • Without changing the sizeof(derived) (tricks should be constexpr/static )
  • Only using defined behavior (no undefined behavior reinterpret_cast for example)
  • The access should be in O(1) complexity

In other words, the layout of the code that I would like to not change should be:

#include <array>
#include <iostream>

template <std::size_t> struct base {
    std::size_t value;
};

struct derived: base<0>, base<1> {
    /*  things can be added here */
    constexpr std::size_t& operator[](std::size_t i) noexcept {
        /*  things can be added here */
    }
    constexpr const std::size_t& operator[](std::size_t i) const noexcept {
        /*  things can be added here */
    }
};

int main(int, char**) {
    derived x{42, 84};
    std::cout << sizeof(base<0>) + sizeof(base<1>) << " " << sizeof(derived);
    std::cout << std::endl;
    std::cout << x[0] << " " << x[1];
    std::cout << std::endl;
    return 0;
}

QUESTION : Why is the current trick not working, and is there a way to make it work?

EDIT : Seems to be a GCC bug. I reported it here . Comparison with clang here .

EXTRA QUESTION (to language lawyers) : Is it a GCC bug, or is it undefined behavior according to the C++17 standard?

This looks like a GCC bug. Your original code produces the expected output with Clang.

One workaround for GCC that I was able to find is to turn members into a static member function:

static constexpr array_type members() noexcept {
    return {&base<0>::value, &base<1>::value};
}

constexpr std::size_t& operator[](std::size_t i) noexcept {
    return this->*members()[i];
}

It is most probably GCC bug as noted in comments. Nonetheless there is funny workaround for this which using std::tuple instead of std::array :

#include <tuple>
#include <array>
#include <iostream>

template <std::size_t> struct base {
    std::size_t value;
};


template<class T, class Tuple, std::size_t... Indx>
constexpr auto to_arr_h(const T& val, const Tuple& t, std::index_sequence<Indx...>) {
    return std::array<std::size_t, sizeof...(Indx)>{val.*(std::get<Indx>(t))...};
}

template<class T, class... Ts>
constexpr auto to_arr(const T& val, const std::tuple<Ts...>& t) {
    return to_arr_h(val, t, std::make_index_sequence<sizeof...(Ts)>{});
}

struct derived: base<0>, base<1> {
    static constexpr auto members = std::make_tuple(&base<0>::value, &base<1>::value);
    constexpr std::size_t& operator[](std::size_t i) noexcept {
        return to_arr(*this, members)[i];
    }
    constexpr const std::size_t& operator[](std::size_t i) const noexcept {
        return to_arr(*this, members)[i];
    }
};

int main(int, char**) {
    derived x{42, 84};
    std::cout << sizeof(base<0>) + sizeof(base<1>) << " " << sizeof(derived);
    std::cout << std::endl;
    std::cout << x[0] << " " << x[1];
    std::cout << std::endl;
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM