class 模板的内部 class 的类似元组的结构化绑定
Tuple-like structured binding for an inner class of a class template
我想为 class 模板的内部 class 提供结构化绑定。我怎样才能将 std::tuple_size
专门化为那个内部 class?
我无法对数据成员使用结构化绑定,因为内部class可能是与该功能不兼容的类型。所以我需要提供类似元组的结构化绑定。
要为内部 class 提供这样的功能,我需要在 namespace std
中部分特化 std::tuple_size
。问题是我得到了参数 T
(外部 class 的)的 非推导上下文 。
我知道我可以将内部的 class 放在全局命名空间中,从而解决所有问题,但是有什么方法可以在保持内部 class 的情况下获得相同的结果?
#include <tuple>
template<typename T>
class Collection
{
public:
struct Element
{
int id;
T actual_element;
};
//...
};
namespace std
{
template<typename T>
struct tuple_size<typename Collection<T>::Element> // Error! Non-deduced context
: std::integral_constant<std::size_t, 2> {};
}
//Collection iterators...
int main()
{
Collection<int> collection;
for (auto & [id, element] : collection) //what I'd like to achieve
//do some stuff...
}
您不必为这种情况提供绑定:Element
已经可以按原样分解:
struct Element { int i, j; };
auto [i, j] = Element{2, 3}; // ok
但是,假设 Element
实际上更复杂并且需要自定义绑定,那么是的 - 您需要将其移出。但是,它 不需要 位于全局命名空间中。它可能在其他地方:
namespace detail {
template <typename T> struct Element { ... };
}
template<typename T>
class Collection
{
public:
using Element = detail::Element<T>;
friend Element;
// ...
};
到那时,专门化绑定就很简单了。没有办法绕过 † 因为正如您所指出的那样,专注于 Collection<T>::Element
是一个非推导的上下文。
†缺少一种新的语言功能,可以让您在 class 正文中选择加入结构化绑定。有这样一篇论文,P1096,但是提交的时候被拒了。这并不是说新提案不能做得更好。
我不认为以下解决方案是解决该问题的最优雅(或根本不优雅)的解决方案。但是,它通过 using/exploiting 实现了为模板 class Element
的内部 class Element
添加结构绑定支持 using/exploiting 一个 C++20 概念非常具体的目的。
鉴于 get
成员函数也可以声明为自由函数,可以使用以下模式为内部 classes 添加结构绑定支持 cannot/may 不被修改(例如,遗留、第 3 方)。这个概念应该相应地调整。
在线试用
代码:
#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
}
总则:
#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;
}
我想为 class 模板的内部 class 提供结构化绑定。我怎样才能将 std::tuple_size
专门化为那个内部 class?
我无法对数据成员使用结构化绑定,因为内部class可能是与该功能不兼容的类型。所以我需要提供类似元组的结构化绑定。
要为内部 class 提供这样的功能,我需要在 namespace std
中部分特化 std::tuple_size
。问题是我得到了参数 T
(外部 class 的)的 非推导上下文 。
我知道我可以将内部的 class 放在全局命名空间中,从而解决所有问题,但是有什么方法可以在保持内部 class 的情况下获得相同的结果?
#include <tuple>
template<typename T>
class Collection
{
public:
struct Element
{
int id;
T actual_element;
};
//...
};
namespace std
{
template<typename T>
struct tuple_size<typename Collection<T>::Element> // Error! Non-deduced context
: std::integral_constant<std::size_t, 2> {};
}
//Collection iterators...
int main()
{
Collection<int> collection;
for (auto & [id, element] : collection) //what I'd like to achieve
//do some stuff...
}
您不必为这种情况提供绑定:Element
已经可以按原样分解:
struct Element { int i, j; };
auto [i, j] = Element{2, 3}; // ok
但是,假设 Element
实际上更复杂并且需要自定义绑定,那么是的 - 您需要将其移出。但是,它 不需要 位于全局命名空间中。它可能在其他地方:
namespace detail {
template <typename T> struct Element { ... };
}
template<typename T>
class Collection
{
public:
using Element = detail::Element<T>;
friend Element;
// ...
};
到那时,专门化绑定就很简单了。没有办法绕过 † 因为正如您所指出的那样,专注于 Collection<T>::Element
是一个非推导的上下文。
†缺少一种新的语言功能,可以让您在 class 正文中选择加入结构化绑定。有这样一篇论文,P1096,但是提交的时候被拒了。这并不是说新提案不能做得更好。
我不认为以下解决方案是解决该问题的最优雅(或根本不优雅)的解决方案。但是,它通过 using/exploiting 实现了为模板 class Element
的内部 class Element
添加结构绑定支持 using/exploiting 一个 C++20 概念非常具体的目的。
鉴于 get
成员函数也可以声明为自由函数,可以使用以下模式为内部 classes 添加结构绑定支持 cannot/may 不被修改(例如,遗留、第 3 方)。这个概念应该相应地调整。
在线试用
代码:
#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
}
总则:
#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;
}