结构化绑定宽度
Structured bindings width
是否有可能确定我应该使用结构化绑定语法在方括号中指定多少变量名来匹配普通右侧struct
的数据成员数量?
我想做一个泛型库的一部分,它使用结构化绑定将任意类分解为其组成部分。目前没有可变版本的结构化绑定(而且,我认为,不能为当前提出的语法),但我的第一个想法是使一些函数decompose()
的一组重载,它执行分解struct
参数成一组它的成分。decompose()
应该通过参数(即struct
)数据成员的数量来重载。目前constexpr if
语法也可以用来分派这个。但是我如何模拟类似于sizeof...
操作符的东西来实现上述目的?我不能在SFINAE结构的某个地方使用auto [a, b, c]
语法,因为它是一个分解声明和AFAIK任何声明都不能在decltype
内使用,我也不能在lambda函数的主体中使用它,因为lambda函数也不能在模板参数内使用。
当然我想有内置的操作符(语法像sizeof[] S
/sizeof[](S)
的class S
),但像下面的东西也是可以接受的:
template< typename type, typename = void >
struct sizeof_struct
{
};
template< typename type >
struct sizeof_struct< type, std::void_t< decltype([] { auto && [p1] = std::declval< type >(); void(p1); }) > >
: std::integral_constant< std::size_t, 1 >
{
};
template< typename type >
struct sizeof_struct< type, std::void_t< decltype([] { auto && [p1, p2] = std::declval< type >(); void(p1); void(p2); }) > >
: std::integral_constant< std::size_t, 2 >
{
};
... etc up to some reasonable arity
也许constexpr
lambda将允许我们使用它们作为模板的参数。你觉得呢?
即将到来的概念可能吗?
struct two_elements {
int x;
double y;
};
struct five_elements {
std::string one;
std::unique_ptr<int> two;
int * three;
char four;
std::array<two_elements, 10> five;
};
struct anything {
template<class T> operator T()const;
};
namespace details {
template<class T, class Is, class=void>
struct can_construct_with_N:std::false_type {};
template<class T, std::size_t...Is>
struct can_construct_with_N<T, std::index_sequence<Is...>, std::void_t< decltype(T{(void(Is),anything{})...}) >>:
std::true_type
{};
}
template<class T, std::size_t N>
using can_construct_with_N=details::can_construct_with_N<T, std::make_index_sequence<N>>;
namespace details {
template<std::size_t Min, std::size_t Range, template<std::size_t N>class target>
struct maximize:
std::conditional_t<
maximize<Min, Range/2, target>{} == (Min+Range/2)-1,
maximize<Min+Range/2, (Range+1)/2, target>,
maximize<Min, Range/2, target>
>
{};
template<std::size_t Min, template<std::size_t N>class target>
struct maximize<Min, 1, target>:
std::conditional_t<
target<Min>{},
std::integral_constant<std::size_t,Min>,
std::integral_constant<std::size_t,Min-1>
>
{};
template<std::size_t Min, template<std::size_t N>class target>
struct maximize<Min, 0, target>:
std::integral_constant<std::size_t,Min-1>
{};
template<class T>
struct construct_searcher {
template<std::size_t N>
using result = ::can_construct_with_N<T, N>;
};
}
template<class T, std::size_t Cap=20>
using construct_airity = details::maximize< 0, Cap, details::construct_searcher<T>::template result >;
这是对T
从0到20的最长构造度进行二分查找。20是一个常量,您可以在编译时和内存成本上随意增加它。
生活例子。
如果结构中的数据不能从其自身类型的右值构造,则在c++ 14中无法工作,但我相信在c++ 17中会出现保证省略(!)
将其转换为结构化绑定需要的不仅仅是一堆手动代码。但是一旦你有了,你应该能够问一些问题,比如"这个struct
的第三种类型是什么"等等。
如果一个struct
可以被分解成结构化绑定,而不需要完成tuple_size
的工作,那么它的复杂度决定了它需要多少变量。
不幸的是,std::tuple_size
即使在c++ 17中也不是SFINAE友好的。但是,使用tuple_size
部分的类型也需要ADL-enable std::get
。
创建failure_tag get<std::size_t>(Ts const&...)
的命名空间using std::get
。使用它来检测它们是否覆盖了类型(!std::is_same< get_type<T,0>, failure_tag >{}
)上的get<0>
,如果是,则沿着tuple_element
路径确定空气质量。将结果元素填充到decltype(get<Is>(x))
的std::tuple
中并返回。
如果失败,使用上面的construct_airity
,并使用它来找出如何在类型上使用结构化绑定。为了统一,我可能会把它发送到std::tie
。
我们现在有了tuple_it
,它接受任何类似结构化绑定的东西,并将其转换为引用或值的元组。现在两条路径都收敛了,您的泛型代码更容易了!
还有一种线性方法可以找到"聚合度"(尽管,也是在相同严格的情况下,如在接受的答案中):
#include <type_traits>
#include <utility>
#include <tuple>
struct filler { template< typename type > operator type && (); };
template< typename aggregate,
typename index_sequence = std::index_sequence<>,
typename = void >
struct aggregate_arity
: index_sequence
{
};
template< typename aggregate,
std::size_t ...indices >
struct aggregate_arity< aggregate,
std::index_sequence< indices... >,
std::void_t< decltype(aggregate{(indices, std::declval< filler >())..., std::declval< filler >()}) > >
: aggregate_arity< aggregate,
std::index_sequence< indices..., sizeof...(indices) > >
{
};
template< std::size_t index, typename type >
constexpr
decltype(auto)
get(type & value) noexcept
{
constexpr std::size_t arity = aggregate_arity< std::remove_cv_t< type > >::size();
if constexpr (arity == 1) {
auto & [p1] = value;
if constexpr (index == 0) {
return (p1);
} else {
return;
}
} else if constexpr (arity == 2) {
auto & [p1, p2] = value;
if constexpr (index == 0) {
return (p1);
} else if constexpr (index == 1) {
return (p2);
} else {
return;
}
} else if constexpr (arity == 3) {
auto & [p1, p2, p3] = value;
if constexpr (index == 0) {
return (p1);
} else if constexpr (index == 1) {
return (p2);
} else if constexpr (index == 2) {
return (p3);
} else {
return;
}
} else /* extend it by yourself for higher arities */ {
return;
}
}
// main.cpp
#include <cstdlib>
#include <cassert>
namespace
{
using S = struct { int i; char c; bool b; };
S s{1, '2', true};
decltype(auto) i = get< 0 >(s);
decltype(auto) c = get< 1 >(s);
decltype(auto) b = get< 2 >(s);
static_assert(std::is_same< decltype(i), int & >{});
static_assert(std::is_same< decltype(c), char & >{});
static_assert(std::is_same< decltype(b), bool & >{});
static_assert(&i == &s.i);
static_assert(&c == &s.c);
static_assert(&b == &s.b);
}
int
main()
{
assert(i == 1);
assert(c == '2');
assert(b == true);
return EXIT_SUCCESS;
}
目前get()
的参数不能有const
顶级类型限定符(即type
可以是&&
和&
,但不能是const &
和const &&
),由于错误。
生活例子。
从Yakk中吸取思想,并应用c++20的特性,如概念和模板化lambdas,使其更紧凑:
template<std::size_t N>
struct anything {
template<class T> operator T() const;
};
template<class T, std::size_t... Ints>
concept Constructible = requires {
T{ anything<Ints>{}... };
};
template<class T, std::size_t N>
constexpr auto is_constructible() {
constexpr auto unpack = [&]<std::size_t... Ints>(std::index_sequence<Ints...>) {
return Constructible<T, Ints...>;
};
return unpack(std::make_index_sequence<N>{});
}
template<class T, std::size_t N = 0u, bool found = false>
constexpr auto find_struct_arity() {
constexpr auto constructible = is_constructible<T, N>();
if constexpr (found && !constructible) {
return N - 1;
}
else if constexpr (constructible) {
return find_struct_arity<T, N + 1, true>();
}
else {
return find_struct_arity<T, N + 1, found>();
}
}
这是对构造性的线性搜索,如果它不再适用于N
花括号,它将返回N - 1
作为结果。示例用法(Godbolt链接):
struct foo2 {
int a;
double b;
};
struct foo5 {
int a;
double b;
foo2 c;
char* d;
std::array<int, 10> e;
};
int main() {
static_assert(find_struct_arity<foo2>() == 2);
static_assert(find_struct_arity<foo5>() == 5);
}
- 在基于范围的for循环中使用结构化绑定声明
- 使用结构化绑定'Reflection'
- 为什么结构化绑定不使用"auto&"返回对结构成员的引用,而是返回成员本身
- 为什么 boost::comb 对结构化绑定的支持缺少结构化绑定机制对 boost::tuples::cons 的适应?
- 结构化绑定初始值设定项表单 { 赋值表达式 } 对于 clang 上的数组类型失败
- 在 C++14 中手动实现结构化绑定
- 为什么结构化绑定不支持可变数组?
- 在只读(即 const)访问器上执行结构化绑定的最佳实践是什么?
- 带有 std::minmax 和 rvalues 的结构化绑定
- 在无序映射的结构化绑定中推导类型
- 为什么基于范围的 for 循环中的结构化绑定只是一个副本而不是引用?
- 您自己的类型的结构化绑定,不是结构或元组(通过公共成员函数)
- 结构化绑定:遍历元组的双端面
- 结构化绑定是否适用于 std::vector?
- 结构化绑定,无需复制即可获取子向量的连续元素
- 成员变量的结构化绑定
- 结构化绑定语法是否可以在多态 lambda 中使用
- 如何 std::绑定模板化函数
- boost::绑定模板化的函子
- 绑定模板化的标准库函数