Щоб клас із приватними полями (або непублічною структурою доступу) підтримував structured binding, потрібен tuple-like протокол:

  1. Спеціалізувати std::tuple_size — кількість елементів.
  2. Спеціалізувати std::tuple_element для кожного індексу — типи елементів.
  3. Надати 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 — окрема нотатка.