Для tuple-like протоколу structured binding зазвичай пишуть чотири get: &, const &, &&, const &&. У C++23 deducing this дозволяє замінити їх одним шаблонним методом, а категорію значення поля прокинути через std::forward_like (<utility>).

Реалізація

#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, typename Self>
    constexpr decltype(auto) get(this Self&& self) noexcept {
        if constexpr (Index == 0) {
            return std::forward_like<Self>(self.id_);
        } else if constexpr (Index == 1) {
            return std::forward_like<Self>(self.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; };
}

tuple_size / tuple_element лишаються як раніше.

Як виводиться Self

Параметр this Self&& self — універсальне посилання на об’єкт виклику:

Виклик на Self
User& User&
const User& const User&
тимчасовий User&& User (далі Self&& → User&&)

Навіщо std::forward_like

std::forward<Self>(self) перенаправляє сам self. Потрібно перенаправити поле (self.id_, self.name_) з тією ж константністю й категорією значення, що й у Self.

std::forward_like<Self>(field) саме це й робить:

Self результат для std::string поля
User& std::string&
const User& const std::string&
User / User&& std::string&&

Порівняння з ручними перевантаженнями: замість чотирьох методів і ручних std::move / (field) — один шаблон і forward_like.

Детальніше про std::forward_like і заміну CRTP через deducing this — у наступних нотатках.