在我之前的博客文章中,我们讨论了static constexpr std::integral_constant
在编译时指定范围大小的习惯用法。与标准不同,think-cell 的范围库已经原生支持编译时大小,因此我渴望尝试那里的习惯用法,看看它在实践中的效果如何。
我们有两种方法来查询大小:一种size
返回整数(如 )的函数std::ranges::size
,以及一种constexpr_size
变量模板,用于确定给定范围类型的编译时大小。在后一种情况下,我们可以通过创建类型constexpr_size
的变量模板来直接应用该习惯用法std::integral_constant
。这样用户就可以在界面中拥有充分的灵活性:
剩下的就是tc::constexpr_size
. 现状是使用特质专业化。请注意,还提供tc::constexpr_size
自动支持的实现。tc::size
我们可以static constexpr std::integral_constant
在实施中使用吗?
我想改变它以某种方式利用这个static constexpr std::integral_constant
习语。也就是说,检查作为默认实现的tc::constexpr_size
格式良好性的实现。Rng::size
然后,我们只需要特征特化来为我们无法控制的类型提供实现,例如std::array
. 虽然它对于尺寸总是已知的范围工厂非常有效,例如std::array
、tc::empty_range
或tc::all_values<Enum>
,但它不适用于范围适配器。
考虑tc::transform_adaptor
(our std::ranges::transform_view
),它返回一个范围,其元素是通过应用函数转换的基础范围元素。至关重要的是,它不会更改范围的大小:如果基础范围rng
有N
元素,tc::transform(rng, fn)
则也有N
元素。因此我们希望透明地转发大小属性rng
:
如果rng
具有constexpr
大小(即tc::constexpr_size<Rng>
格式良好),则 也应如此tc::transform(rng, fn)
。
否则,如果rng
具有运行时大小(即tc::size(rng)
格式良好),则应该如此tc::transform(rng, fn)
。
否则,它既没有constexpr
也没有运行时大小。
static constexpr std::integral_constant
使用for 的天真的尝试tc::constexpr_size
可能如下所示:
这里的问题是“某种程度上受到约束”的注释:与static
函数不同,成员变量不能受到约束,也不能用成员函数重载。constexpr
因此,只有在Rng
有大小的情况下,我们才必须有条件地继承大小constexpr
,这很丑陋。
我们也没有从使用中得到任何好处static constexpr std::integral_constant
——用户不应该直接调用rng.size()
,他们应该使用tc::size
ortc::constexpr_size
来代替。static constexpr std::integral_constant
所以在实现中使用没有任何好处。
有条件返回std::integral_constant
自.size()
因此,我们只有一个size()
函数返回std::size_t
or ,即范围大小的std::integral_constant
“最”版本。constexpr
这种方法继续适用于范围工厂,但现在我们也可以编写我们的适配器:
相应的特化tc::constexpr_size
调用里面的函数decltype
来得到结果:
运行时版本tc::size
不需要更改,因为范围仍然有一个.size()
返回大小的成员函数,只需在返回类型本身中进行编码即可。
避免代码重复
虽然该方法很好,但存在一些代码重复。对于更复杂的范围(例如 ),这一点尤其明显tc::concat_adaptor
,其大小是所有子范围大小的总和:
我们计算相同的值两次:一次作为 a std::integral_constant
,一次作为std::size_t
。我们可以通过使用辅助函数分割大小计算和结果包装来统一它:
辅助函数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_type
or的谓词的支持std::false_type
。这样,我们就可以std::tuple
使用与按值过滤运行时范围相同的机制按类型进行过滤。