Щоб клас із приватними полями (або непублічною структурою доступу) підтримував structured binding, потрібен tuple-like протокол:
- Спеціалізувати
std::tuple_size— кількість елементів. - Спеціалізувати
std::tuple_elementдля кожного індексу — типи елементів. - Надати
get<N>()— доступ до значень (метод або вільна функція через ADL).
Приклад: User → (id, name)
#include <cstddef>
#include <string>
#include <utility>
class User {
private:
int id_;
std::string name_;
public:
User(int id, std::string name)
: id_(id), name_(std::move(name)) {}
template <std::size_t Index>
decltype(auto) get() & {
if constexpr (Index == 0) return (id_);
else if constexpr (Index == 1) return (name_);
}
template <std::size_t Index>
decltype(auto) get() const & {
if constexpr (Index == 0) return (id_);
else if constexpr (Index == 1) return (name_);
}
template <std::size_t Index>
decltype(auto) get() && {
if constexpr (Index == 0) return std::move(id_);
else if constexpr (Index == 1) return std::move(name_);
}
template <std::size_t Index>
decltype(auto) get() const && {
if constexpr (Index == 0) return std::move(id_);
else if constexpr (Index == 1) return std::move(name_);
}
};
namespace std {
template <>
struct tuple_size<User> : std::integral_constant<std::size_t, 2> {};
template <>
struct tuple_element<0, User> {
using type = int;
};
template <>
struct tuple_element<1, User> {
using type = std::string;
};
} // namespace std
Використання:
User user{101, "Oleksii"};
auto& [id, name] = user;
name = "Dmytro";
const auto& [cid, cname] = user;
auto [id_copy, name_copy] = user;
auto [moved_id, moved_name] = User{202, "Temporary"};
Нюанси
Пошук get. Спочатку метод e.get<N>(). Якщо його немає — вільна функція через ADL: get<N>(e). Вільну функцію зручно писати, коли клас змінювати не можна:
template <std::size_t Index>
decltype(auto) get(const User& u) {
if constexpr (Index == 0) return u.get_id();
else if constexpr (Index == 1) return u.get_name();
}
Дужки в return (id_). Разом із decltype(auto) (id_) — lvalue-вираз, тож повертається int& / const int&. Без дужок return id_; для звичайного члена типу int дає копію (int). Для &&-перевантажень поля переміщують через std::move.
Чотири категорії виклику. Без && / const && розпакування тимчасового об’єкта може копіювати замість move або не скомпілюватися так, як очікуєте.
У C++23 чотири перевантаження get можна згорнути в один метод через deducing this і std::forward_like — окрема нотатка.