Reputation: 160
Stroustrup mentions in his book A Tour of C++ (section 3.6.3) that it is possible to do structured bindings for a class whose data is accessible through member functions. Here's the example code he gives:
complex<double> z = {1,2}
auto [re,im] = z+2
Then he says,
Mapping a
complex<double>
to two local variables, such asre
andim
is feasible and efficient, but the technique for doing so is beyond the scope of this book.
What is he referring to by "the technique for doing so"?
Upvotes: 2
Views: 113
Reputation: 96906
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>();
}
};
Upvotes: 3