如何定义一个可以结构化绑定的对象的概念?
How to define a concept of a object that is can be structured binding?
我想定义一个concept
可以检测类型T
是否可以结构化绑定:
template <typename T>
concept two_elements_structured_bindable = requires (T t) {
auto [x, y] = t;
};
但这无法编译。有没有正确的方法来定义这样的 concept
?
没有
结构化绑定分三种情况:
数组。这很容易检测到。
Tuple-like。您可以轻松检查 std::tuple_size<E>::value
是否有效,然后检查 std::tuple_element<I, E>::type
是否作为所有正确类型的类型有效。但是 get
的情况更难,因为你必须处理成员与 non-member... 但除此之外我认为可行。
所有 public(是的,技术上可访问的)成员都是同一 class 的直接成员的类型。这是不可能用目前的技术检测到的。我认为 magic_get
可以处理 struct X { int a, b; };
但 struct Y : X { };
和 struct Z { X& x; };
都不能。您需要进行适当的反思才能检查这种情况。
从 C++20 开始,您将需要某种编译器固有功能来执行此操作。
使用 C++20,您可以定义一个 concept
来识别 C 风格数组 和 类元组类型 作为结构可绑定。但它无法识别基于 public-only 字段的结构可绑定类型。
可以实施的可能概念(参见完整实施here):
template<typename T, std::size_t N>
concept structure_bindable =
(std::is_array_v<T> && (std::extent_v<T> == N)) ||
((std::tuple_size_v<T> == N) && has_get_for_size<T, N>::value);
template<typename T, typename... Ts>
concept structure_bindable_with =
structure_bindable<T, sizeof...(Ts)>
&& is_get_N<T, Ts...>(std::make_index_sequence<sizeof...(Ts)>{});
template<typename T, size_t N, typename Expected>
concept structure_bindable_with_N =
structure_bindable<T, N>
&& is_get_N<T, N-1, Expected>();
旁注:它可以通过编译器的内在特性来实现
for example - here for clang(由 Avi Lachmish 提供)。
亲爱的朋友@Dvir Yitzchaki pointed out to me that based on the proposed pattern matching syntax by Herb Sutter, you can identify all stucture bindable cases, based on as
check inside a concept
, not yet in C++20, but already implemented in Circle compiler.
Herb Sutter 与 Circle 编译器实现者 Sean Baxter 一起提出了 is
和 as
模式匹配的想法,作为 Herb 在 CppCon 2021 see here 上的演讲的一部分。
根据他们的谈话,Dvir 想到了我后来详细阐述的想法 working implementation on Circle compiler:
template <typename T>
concept two_elements_structured_bindable = structured_bindable<T>
&& !single_element_structured_bindable<T>
&& two_elements_structured_bindable_<T>;
基于此:
template <typename T>
concept structured_bindable = requires (T t) {
t as [...]; // note: not supported by C++20
};
template <typename T>
struct single_element_structured_bindable_wrapper {
auto first() {
auto[a, ...] = std::declval<T>(); // note: not supported by C++20
return a;
}
using first_type = decltype(first());
};
template <typename T>
concept single_element_structured_bindable = structured_bindable<T>
&& requires (T t) {
{t as [single_element_structured_bindable_wrapper<T>::first_type]};
};
并且:
template <typename T>
struct two_elements_structured_bindable_wrapper {
auto first() {
auto[a, ...] = std::declval<T>(); // note: not supported by C++20
return a;
}
auto second() {
auto[a, b, ...] = std::declval<T>(); // note: not supported by C++20
return b;
}
using first_type = decltype(first());
using second_type = decltype(second());
};
template <typename T>
concept two_elements_structured_bindable_ = requires (T t) {
{t as [
two_elements_structured_bindable_wrapper<T>::first_type,
two_elements_structured_bindable_wrapper<T>::second_type
]};
};
请注意,它支持检查所有类型的结构绑定:
static_assert(!two_elements_structured_bindable<std::tuple<int, int, int>>);
static_assert(!two_elements_structured_bindable<std::tuple<int>>);
static_assert(!two_elements_structured_bindable<int>);
static_assert(two_elements_structured_bindable<std::tuple<int, int>>);
static_assert(!two_elements_structured_bindable<std::array<int, 3>>);
static_assert(!two_elements_structured_bindable<std::array<int, 1>>);
static_assert(two_elements_structured_bindable<std::array<int, 2>>);
struct Vec3 { float x, y, z; };
static_assert(!two_elements_structured_bindable<Vec3>);
struct Vec2 { float x, y; };
static_assert(two_elements_structured_bindable<Vec2>);
在 a CoreCpp meetup, Dvir poured cold water on my solution, with a much shorter one 中提出上述解决方案后:
struct anything // std::any is not good enough for that
{
template<typename T>
anything(T&&) {}
};
template<typename T>
concept twople = requires(T t)
{
t as [anything, anything];
};
我仍然保留上面的长解决方案,因为在我看来它对其他实现有一些价值。
如果您想限制要绑定的类型,您可能更喜欢使用另一种方法,它再次依赖于模式匹配语法,另一种建议 concept
:
template <typename T, typename... Ts>
concept TupleLike = requires (T t) {
{t as [Ts...]}; // note: not supported by C++20
};
可以允许这样的约束:
void foo(TupleLike<double, double, double> auto tup) {
auto[a, b, c] = tup; // 3 doubles
// ...
}
Above code 再次基于模式匹配语法,在 C++ 中尚不可用(自 C++20 起),但已在 Circle 编译器中实现。
现在,为了支持 structured_bindable<Size>
的更通用概念,需要使用另一个未来的 C++ 功能,它允许 sizeof...(T)
在 T
上可变参数包,而是任何结构可绑定类型。此功能可能是 p1858 or a related proposal. And again, it is already supported in Circle compiler.
的一部分
这允许 this very simple implementation(再次由 Dvir 提议):
template <typename T, size_t SIZE>
concept has_size_of = sizeof...(T) == SIZE;
template <typename T>
concept structured_bindable = requires (T t) {
t as [...];
};
template <typename T, size_t SIZE>
concept structured_bindable_with = structured_bindable<T> && has_size_of<T, SIZE>;
因此允许约束可以绑定到一个确切的给定数字,例如:
void foo(const structured_bindable_with<2> auto& v) {
const auto&[a, b] = v;
std::cout << a << ", " << b << std::endl;
}
事实上,如果能够提供 sizeof...
成为一个特性,表明你是一个结构可绑定类型(包括可变参数包本身!),那么上面的概念 can simply become:
template <typename T, size_t SIZE>
concept structured_bindable_with = (sizeof...(T) == SIZE);
我想定义一个concept
可以检测类型T
是否可以结构化绑定:
template <typename T>
concept two_elements_structured_bindable = requires (T t) {
auto [x, y] = t;
};
但这无法编译。有没有正确的方法来定义这样的 concept
?
没有
结构化绑定分三种情况:
数组。这很容易检测到。
Tuple-like。您可以轻松检查
std::tuple_size<E>::value
是否有效,然后检查std::tuple_element<I, E>::type
是否作为所有正确类型的类型有效。但是get
的情况更难,因为你必须处理成员与 non-member... 但除此之外我认为可行。所有 public(是的,技术上可访问的)成员都是同一 class 的直接成员的类型。这是不可能用目前的技术检测到的。我认为
magic_get
可以处理struct X { int a, b; };
但struct Y : X { };
和struct Z { X& x; };
都不能。您需要进行适当的反思才能检查这种情况。
从 C++20 开始,您将需要某种编译器固有功能来执行此操作。
使用 C++20,您可以定义一个 concept
来识别 C 风格数组 和 类元组类型 作为结构可绑定。但它无法识别基于 public-only 字段的结构可绑定类型。
可以实施的可能概念(参见完整实施here):
template<typename T, std::size_t N>
concept structure_bindable =
(std::is_array_v<T> && (std::extent_v<T> == N)) ||
((std::tuple_size_v<T> == N) && has_get_for_size<T, N>::value);
template<typename T, typename... Ts>
concept structure_bindable_with =
structure_bindable<T, sizeof...(Ts)>
&& is_get_N<T, Ts...>(std::make_index_sequence<sizeof...(Ts)>{});
template<typename T, size_t N, typename Expected>
concept structure_bindable_with_N =
structure_bindable<T, N>
&& is_get_N<T, N-1, Expected>();
旁注:它可以通过编译器的内在特性来实现 for example - here for clang(由 Avi Lachmish 提供)。
亲爱的朋友@Dvir Yitzchaki pointed out to me that based on the proposed pattern matching syntax by Herb Sutter, you can identify all stucture bindable cases, based on as
check inside a concept
, not yet in C++20, but already implemented in Circle compiler.
Herb Sutter 与 Circle 编译器实现者 Sean Baxter 一起提出了 is
和 as
模式匹配的想法,作为 Herb 在 CppCon 2021 see here 上的演讲的一部分。
根据他们的谈话,Dvir 想到了我后来详细阐述的想法 working implementation on Circle compiler:
template <typename T>
concept two_elements_structured_bindable = structured_bindable<T>
&& !single_element_structured_bindable<T>
&& two_elements_structured_bindable_<T>;
基于此:
template <typename T>
concept structured_bindable = requires (T t) {
t as [...]; // note: not supported by C++20
};
template <typename T>
struct single_element_structured_bindable_wrapper {
auto first() {
auto[a, ...] = std::declval<T>(); // note: not supported by C++20
return a;
}
using first_type = decltype(first());
};
template <typename T>
concept single_element_structured_bindable = structured_bindable<T>
&& requires (T t) {
{t as [single_element_structured_bindable_wrapper<T>::first_type]};
};
并且:
template <typename T>
struct two_elements_structured_bindable_wrapper {
auto first() {
auto[a, ...] = std::declval<T>(); // note: not supported by C++20
return a;
}
auto second() {
auto[a, b, ...] = std::declval<T>(); // note: not supported by C++20
return b;
}
using first_type = decltype(first());
using second_type = decltype(second());
};
template <typename T>
concept two_elements_structured_bindable_ = requires (T t) {
{t as [
two_elements_structured_bindable_wrapper<T>::first_type,
two_elements_structured_bindable_wrapper<T>::second_type
]};
};
请注意,它支持检查所有类型的结构绑定:
static_assert(!two_elements_structured_bindable<std::tuple<int, int, int>>);
static_assert(!two_elements_structured_bindable<std::tuple<int>>);
static_assert(!two_elements_structured_bindable<int>);
static_assert(two_elements_structured_bindable<std::tuple<int, int>>);
static_assert(!two_elements_structured_bindable<std::array<int, 3>>);
static_assert(!two_elements_structured_bindable<std::array<int, 1>>);
static_assert(two_elements_structured_bindable<std::array<int, 2>>);
struct Vec3 { float x, y, z; };
static_assert(!two_elements_structured_bindable<Vec3>);
struct Vec2 { float x, y; };
static_assert(two_elements_structured_bindable<Vec2>);
在 a CoreCpp meetup, Dvir poured cold water on my solution, with a much shorter one 中提出上述解决方案后:
struct anything // std::any is not good enough for that
{
template<typename T>
anything(T&&) {}
};
template<typename T>
concept twople = requires(T t)
{
t as [anything, anything];
};
我仍然保留上面的长解决方案,因为在我看来它对其他实现有一些价值。
如果您想限制要绑定的类型,您可能更喜欢使用另一种方法,它再次依赖于模式匹配语法,另一种建议 concept
:
template <typename T, typename... Ts>
concept TupleLike = requires (T t) {
{t as [Ts...]}; // note: not supported by C++20
};
可以允许这样的约束:
void foo(TupleLike<double, double, double> auto tup) {
auto[a, b, c] = tup; // 3 doubles
// ...
}
Above code 再次基于模式匹配语法,在 C++ 中尚不可用(自 C++20 起),但已在 Circle 编译器中实现。
现在,为了支持 structured_bindable<Size>
的更通用概念,需要使用另一个未来的 C++ 功能,它允许 sizeof...(T)
在 T
上可变参数包,而是任何结构可绑定类型。此功能可能是 p1858 or a related proposal. And again, it is already supported in Circle compiler.
这允许 this very simple implementation(再次由 Dvir 提议):
template <typename T, size_t SIZE>
concept has_size_of = sizeof...(T) == SIZE;
template <typename T>
concept structured_bindable = requires (T t) {
t as [...];
};
template <typename T, size_t SIZE>
concept structured_bindable_with = structured_bindable<T> && has_size_of<T, SIZE>;
因此允许约束可以绑定到一个确切的给定数字,例如:
void foo(const structured_bindable_with<2> auto& v) {
const auto&[a, b] = v;
std::cout << a << ", " << b << std::endl;
}
事实上,如果能够提供 sizeof...
成为一个特性,表明你是一个结构可绑定类型(包括可变参数包本身!),那么上面的概念 can simply become:
template <typename T, size_t SIZE>
concept structured_bindable_with = (sizeof...(T) == SIZE);