Home > other >  elegant way to convert variadic inheritance members to tuple
elegant way to convert variadic inheritance members to tuple

Time:08-21

consider a type that inherits from multiple classes. I want to iterate over the inherited classes, ideally making a get_tuple() member function that returns a reference tuple for precise manipulation:

struct A { int a; };
struct B { float b; };
struct C { const char* c; };

template<typename ... Ts>
struct collection : Ts...{

    constexpr auto get_tuple() const {
        return std::make_tuple(/*???*/);
    }

    std::string string() const {
        std::ostringstream stream;

        auto tuple = this->get_tuple();

        std::apply([&](auto&& first, auto&&... rest) {
            stream << first;
            ((stream << ", " << rest), ...);
            }, tuple);

        return stream.str();
    }
};

int main() {
    collection<A, B, C> a{ 1, 0.5f, "hello" };
    std::cout << a.string();
}

Is there a nice way to achieve this?


this is one solution I found. Giving each base struct a get() function which will enable the std::make_tuple(Ts::get()...); syntax. the tuple will hold the fundamental types (int, float, const char*) but I am not limiting myself to that, I wouldn't also mind a solution where I receive a tuple of A, B and C's. I delete the inherited get(), since these symbols will appear multiple times in collection:

struct A {
    int a; 
    constexpr auto& get() { return a; }
    constexpr const auto& get() const { return a; }
};
struct B {
    float b;
    constexpr auto& get() { return b; }
    constexpr const auto& get() const { return b; }
};
struct C {
    const char* c;
    constexpr auto& get()  { return c; }
    constexpr const auto& get() const { return c; }
};

template<typename ... Ts>
struct collection : Ts...{
    constexpr auto get() = delete;
    constexpr auto get() const = delete;

    constexpr auto get_tuple() {
        return std::make_tuple(Ts::get()...);
    }
    constexpr auto get_tuple() const {
        return std::make_tuple(Ts::get()...);
    }

    std::string string() const {
        std::ostringstream stream;

        auto tuple = this->get_tuple();

        std::apply([&](auto&& first, auto&&... rest) {
            stream << first;
            ((stream << ", " << rest), ...);
            }, tuple);

        return stream.str();
    }
};

int main() {
    collection<A, B, C> a{1, 0.5f, "hello"};
    std::cout << a.string();
}

this works, but is quite a work around, is there an easy solution / some sweet syntax sugar I missed here?

CodePudding user response:

You could rely on the fact that every single one of the base classes allows for structured binding to work with one variable to extract the members.


/**
 * Our own namespace is used to avoid applying AccessMember to arbitrary types
 */
namespace MyNs
{

struct A { int a; };
struct B { float b; };
struct C { const char* c; };

template<class T>
constexpr auto& AccessMember(T const& val)
{
    auto& [member] = val;
    return member;
}

template<class T>
constexpr auto& AccessMember(T& val)
{
    auto& [member] = val;
    return member;
}

} //namespace MyNs


template<typename ... Ts>
struct collection : Ts...
{

    constexpr auto get_tuple() const
    {
        return std::tuple<decltype(AccessMember(std::declval<Ts>()))...>(AccessMember(static_cast<Ts const&>(*this))...);
    }

    constexpr auto get_tuple()
    {
        return std::tuple<decltype(AccessMember(std::declval<Ts&>()))...>(AccessMember(static_cast<Ts&>(*this))...);
    }

    std::string string() const {
        std::ostringstream stream;

        auto tuple = this->get_tuple();

        std::apply([&](auto&& first, auto&&... rest) {
            stream << first;
            ((stream << ", " << rest), ...);
            }, tuple);

        return stream.str();
    }

};

int main() {
    using MyNs::A;
    using MyNs::B;
    using MyNs::C;

    collection<A, B, C> a{ 1, 0.5f, "hello" };

    std::cout << a.string()<< '\n';
}
  • Related