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';
}