I do not claim the below solution to be the most elegant (or elegant at all) solution for the problem. It, however, achieves to add structure binding support for the inner class Element
of the template class Collection
by using/exploiting a C++20 concept for this very specific purpose.
Given that the get
member functions, can be declared as free functions as well, it is possible to use the below pattern for adding structure binding support for inner classes that cannot/may not be modified (e.g., legacy, 3th party). The concept should be tweaked accordingly.
Try It Online
Code:
#include <concepts>
#include <tuple>
struct Hack
{};
template< typename T >
concept CollectionElement = requires
{
typename T::type;
{ T::hack } -> std::same_as< const Hack& >;
};
namespace std
{
template< ::CollectionElement T >
class tuple_size< T > : public integral_constant< size_t, 2 >
{};
template< ::CollectionElement T >
struct tuple_element< 0u, T >
{
using type = int;
};
template< ::CollectionElement T >
struct tuple_element< 1u, T >
{
using type = typename T::type;
};
}
template<typename T>
class Collection
{
public:
struct Element
{
static constexpr Hack hack = {};
using type = T;
template< std::size_t I >
std::tuple_element_t< I, Element >& get() &
{
if constexpr (I == 0u) return id;
if constexpr (I == 1u) return actual_element;
}
template< std::size_t I >
const std::tuple_element_t< I, Element >& get() const&
{
if constexpr (I == 0u) return id;
if constexpr (I == 1u) return actual_element;
}
template< std::size_t I >
std::tuple_element_t< I, Element >& get() &&
{
if constexpr (I == 0u) return id;
if constexpr (I == 1u) return actual_element;
}
template< std::size_t I >
const std::tuple_element_t< I, Element >& get() const&&
{
if constexpr (I == 0u) return id;
if constexpr (I == 1u) return actual_element;
}
int id;
T actual_element;
};
//...
};
int main()
{
Collection< int >::Element test
{
.id = 3,
.actual_element = 5
};
auto& [id, element] = test;
id = 7;
element = 9;
return id + element; // returns 16
}
General principle:
#include <concepts>
#include <tuple>
template< typename T >
struct Outer
{
using value_type = T;
struct Inner
{
using value_type = typename Outer::value_type;
int m_first = {};
int m_second = {};
};
};
namespace std
{
template< typename T >
requires(std::same_as< T, typename ::Outer< typename T::value_type >::Inner >)
class tuple_size< T > : public integral_constant< size_t, 2 >
{};
template< typename T >
requires(std::same_as< T, typename ::Outer< typename T::value_type >::Inner >)
struct tuple_element< 0u, T >
{
using type = int;
};
template< typename T >
requires(std::same_as< T, typename ::Outer< typename T::value_type >::Inner >)
struct tuple_element< 1u, T >
{
using type = int;
};
}
template< std::size_t I, typename T >
requires(std::same_as< T, typename Outer< typename T::value_type >::Inner >)
[[nodiscard]]
inline std::tuple_element_t< I, T >& get(T& src)
{
if constexpr (I == 0u)
{
return src.m_first;
}
if constexpr (I == 1u)
{
return src.m_second;
}
}
template< std::size_t I, typename T >
requires(std::same_as< T, typename Outer< typename T::value_type >::Inner >)
[[nodiscard]]
inline const std::tuple_element_t< I, T >& get(const T& src)
{
if constexpr (I == 0u)
{
return src.m_first;
}
if constexpr (I == 1u)
{
return src.m_second;
}
}
template< std::size_t I, typename T >
requires(std::same_as< T, typename Outer< typename T::value_type >::Inner >)
[[nodiscard]]
inline std::tuple_element_t< I, T >&& get(T&& src)
{
if constexpr (I == 0u)
{
return src.m_first;
}
if constexpr (I == 1u)
{
return src.m_second;
}
}
template< std::size_t I, typename T >
requires(std::same_as< T, typename Outer< typename T::value_type >::Inner >)
[[nodiscard]]
inline const std::tuple_element_t< I, T >&& get(const T&& src)
{
if constexpr (I == 0u)
{
return src.m_first;
}
if constexpr (I == 1u)
{
return src.m_second;
}
}
int main()
{
Outer< int >::Inner ref;
auto& [f, s] = ref;
f = 3; s = 5;
return ref.m_first + ref.m_second;
}