技术文章

了解最新技术文章

当前位置:首页>技术文章>技术文章
全部 146 常见问题 7 技术文章 139

think cell中范围适配器的编译时大小

时间:2023-11-06   访问量:1035  标签: think-cell,think cell,范围适配器

在我之前的博客文章中,我们讨论了static constexpr std::integral_constant在编译时指定范围大小的习惯用法。与标准不同,think-cell 的范围库已经原生支持编译时大小,因此我渴望尝试那里的习惯用法,看看它在实践中的效果如何。

namespace tc {
	template <typename Rng>
	constexpr auto size(Rng&& rng); // runtime-size of a range, like std::ranges::size
	
	template <typename Rng> requires tc::has_constexpr_size<Rng>
	constexpr auto constexpr_size = …; // compile-time size of a range given its type}
tc::sizetc::constexpr_size

我们有两种方法来查询大小:一种size返回整数(如 )的函数std::ranges::size,以及一种constexpr_size变量模板,用于确定给定范围类型的编译时大小。在后一种情况下,我们可以通过创建类型constexpr_size的变量模板来直接应用该习惯用法std::integral_constant这样用户就可以在界面中拥有充分的灵活性:

template <typename Rng>void foo(Rng&& rng) {
	std::size_t runtime_size = tc::size(rng);
	constexpr std::size_t compile_time_size = tc::constexpr_size<Rng>();
	constexpr std::integral_constant compile_time_size_constant = tc::constexpr_size<Rng>;}
tc::size的接口tc::constexpr_size

剩下的就是tc::constexpr_size现状是使用特质专业化。请注意,还提供tc::constexpr_size自动支持的实现。tc::size

template <typename Rng>struct constexpr_size_impl; // no definitiontemplate <typename Rng>constexpr auto constexpr_size = constexpr_size_impl<std::remove_cvref_t<Rng>>{};// Real implementation delegates all tuple-like types to std::tuple_size, omitted here.template <typename T, std::size_t N>struct constexpr_size_impl<std::array<T, N>> : std::integral_constant<std::size_t, N> {};// more specializations
之前的实施tc::constexpr_size

我们可以static constexpr std::integral_constant在实施中使用吗?

我想改变它以某种方式利用这个static constexpr std::integral_constant习语。也就是说,检查作为默认实现的tc::constexpr_size格式良好性的实现。Rng::size然后,我们只需要特征特化来为我们无法控制的类型提供实现,例如std::array虽然它对于尺寸总是已知的范围工厂非常有效,例如std::arraytc::empty_rangetc::all_values<Enum>,但它不适用于范围适配器。

考虑tc::transform_adaptor(our std::ranges::transform_view),它返回一个范围,其元素是通过应用函数转换的基础范围元素。至关重要的是,它不会更改范围的大小:如果基础范围rngN元素,tc::transform(rng, fn)则也有N元素。因此我们希望透明地转发大小属性rng

static constexpr std::integral_constant使用for 的天真的尝试tc::constexpr_size可能如下所示:

template <typename Rng, typename Fn>struct transform_adaptor {
	// for tc::constexpr_size and tc::size
	static constexpr std::integral_constant size = tc::constexpr_size<Rng>; // somehow constrained
	// for tc::size
	constexpr std::size_t size() const
		requires tc::has_size<Rng> && (!tc::has_constexpr_size<Rng>)
	{
		return tc::size(base_rng());
	}};
天真的尝试

这里的问题是“某种程度上受到约束”的注释:与static函数不同,成员变量不能受到约束,也不能用成员函数重载。constexpr因此,只有在Rng有大小的情况下,我们才必须有条件地继承大小constexpr,这很丑陋。

我们也没有从使用中得到任何好处static constexpr std::integral_constant——用户不应该直接调用rng.size(),他们应该使用tc::sizeortc::constexpr_size来代替。static constexpr std::integral_constant所以在实现中使用没有任何好处。

有条件返回std::integral_constant.size()

因此,我们只有一个size()函数返回std::size_tor ,即范围大小的std::integral_constant“最”版本。constexpr这种方法继续适用于范围工厂,但现在我们也可以编写我们的适配器:

template <typename Rng, typename Fn>struct transform_adaptor {
	// for tc::size and tc::constexpr_size
	constexpr auto size() const& requires tc::has_size<Rng> {
		if constexpr (tc::has_constexpr_size<Rng>)
			return tc::constexpr_size<Rng>;
		else
			return tc::size(base_rng());
	}};
更好的实施

相应的特化tc::constexpr_size调用里面的函数decltype来得到结果:

// Rng has a size function that returns an integral_constant.template <typename Rng> requires requires { decltype(std::declval<Rng&>().size())::value; }struct constexpr_size_impl<Rng> : decltype(std::declval<Rng&>().size()) {};
tc::constexpr_size专业。

运行时版本tc::size不需要更改,因为范围仍然有一个.size()返回大小的成员函数,只需在返回类型本身中进行编码即可。

避免代码重复

虽然该方法很好,但存在一些代码重复。对于更复杂的范围(例如 ),这一点尤其明显tc::concat_adaptor,其大小是所有子范围大小的总和:

template <typename ... Rng>struct concat_adaptor {
	constexpr auto size() const
		requires (tc::has_size<Rng> && ...)
	{
		if constexpr ((tc::has_constexpr_size<Rng> && ...))
			return std::integral_constant<std::size_t, (tc::constexpr_size<Rng>() + ...)>{};
		else
			return std::apply([](auto const& ... base_rng) {
				return (tc::size(base_rng) + ...);
			}, base_rng_tuple);
	}};
size()为了tc::concat_adaptor

我们计算相同的值两次:一次作为 a std::integral_constant,一次作为std::size_t我们可以通过使用辅助函数分割大小计算和结果包装来统一它:

template <auto Fn, typename ... Rng>constexpr auto compute_range_adaptor_size(Rng&&... rng) {
	if constexpr ((tc::has_constexpr_size<Rng> && ...)) {
		auto constexpr value = Fn(tc::constexpr_size<Rng>()...);
		return std::integral_constant<std::size_t, value>{};
	} else {
		auto const value = Fn(tc::size(std::forward<Rng>(rng))...);
		return value;
	}}template <typename ... Rng>struct concat_adaptor {
	constexpr auto size() const
		requires (tc::has_size<Rng> && ...)
	{
		return std::apply([](auto const& ... base_rng) {
			return tc::compute_range_adaptor_size<[](auto const ... n) {
				return (n + ...);
			}>(base_rng...);
		}, base_rng_tuple);
	}};
size()用于tc::concat_adaptor使用tc::compute_range_adaptor_size()助手

辅助函数compute_range_adaptor_size()获取所有子范围并计算constexpr其大小的最大版本。然后将其传递给 lambda 来计算派生大小,并以大多数constexpr方式返回。请注意,lambda 必须作为非类型模板参数传递,因此我们可以在上下文中使用其结果constexpr

concat_adaptor::size()然后只需要compute_range_adaptor_size()使用适当的 lambda 和子范围进行调用,它就会自动工作。

结论

范围适配器的大小可以在编译时或运行时获得。std::integral_constant我们可以通过尽可能返回constexpr尺寸的最新版本来轻松转发该信息。

该模式可以应用于更多情况。例如,我最近添加了对tc::filter返回std::true_typeor的谓词的支持std::false_type这样,我们就可以std::tuple使用与按值过滤运行时范围相同的机制按类型进行过滤。


上一篇:think cell中如何使用键盘快捷键?

下一篇:think cell中默认情况下不同

发表评论:

评论记录:

未查询到任何数据!

免费通话

24小时免费咨询

请输入您的联系电话,座机请加区号

免费通话

微信扫一扫

微信联系
返回顶部