Check out cppreference.
You specialize std::tuple_size<T> and std::tuple_element<T> for your type, and provide get<I>(...) either in the same namespace (where it can be found by ADL) or as a member function.
get needs 4 overloads: for T &, const T &, T &&, and const T &&, though in some cases you might be able to get away with less overloads.
tuple_size and tuple_element only need one specialization each. You don't need to manually specialize for const and/or volatile T, since the standard library already defines generic specializations for const and/or volatile types that forward to the non-qualified specializations.
Example:
#include <cstddef>
#include <iostream>
#include <type_traits>
#include <utility>
struct A
{
int unused;
int x;
float y;
std::string z;
template <std::size_t I>
friend auto &get(A &a)
{
static_assert(I < 3);
if constexpr (I == 0)
return a.x;
else if constexpr (I == 1)
return a.y;
else
return a.z;
}
template <std::size_t I>
friend const auto &get(const A &a)
{
return get<I>(const_cast<A &>(a));
}
template <std::size_t I>
friend auto &&get(A &&a)
{
return get<I>(static_cast<A &>(a));
}
template <std::size_t I>
friend const auto &&get(const A &&a)
{
return get<I>(const_cast<A &&>(a));
}
};
template <>
struct std::tuple_size<A> : std::integral_constant<std::size_t, 3> {};
template <std::size_t I>
struct std::tuple_element<I, A>
{
static_assert(I < 3);
using type =
std::conditional_t<I == 0, int,
std::conditional_t<I == 1, float,
std::string>>;
};
auto [f, g, h] = A{};
Variation 1: get defined outside of the class.
struct A
{
int unused;
int x;
float y;
std::string z;
};
template <std::size_t I>
auto &get(A &a)
{
static_assert(I < 3);
if constexpr (I == 0)
return a.x;
else if constexpr (I == 1)
return a.y;
else
return a.z;
}
template <std::size_t I>
const auto &get(const A &a)
{
return get<I>(const_cast<A &>(a));
}
template <std::size_t I>
auto &&get(A &&a)
{
return get<I>(static_cast<A &>(a));
}
template <std::size_t I>
const auto &&get(const A &&a)
{
return get<I>(const_cast<A &&>(a));
}
Variation 2: get is a non-static member function.
struct A
{
int unused;
int x;
float y;
std::string z;
template <std::size_t I>
auto &get() &
{
static_assert(I < 3);
if constexpr (I == 0)
return x;
else if constexpr (I == 1)
return y;
else
return z;
}
template <std::size_t I>
const auto &get() const &
{
return const_cast<A *>(this)->get<I>();
}
template <std::size_t I>
auto &&get() &&
{
return get<I>();
}
template <std::size_t I>
const auto &&get() const &&
{
return get<I>();
}
};