如果我理论上有一个像
这样的整数序列std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>
如何使用某些编译时谓词对其进行过滤以获得可能较小的std::integer_sequence<int, ...>
?
为了论证,让我们说我只想要偶数值, 这导致了问题&#34;如何使以下static_assert(或接近的东西)通过?&#34;
static_assert(std::is_same_v<std::integer_sequence<int, 0, 2, 4, 6, 8>,
decltype(FilterEvens(std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}))>,
"Integer sequences should be equal");
这个问题的灵感来自于考虑如何在两个位集(this question)之间删除重复项,假设我们可以将位集表示为仅包含0和1的integer_sequences
。如果可以的话,可以获得奖励积分以这种方式解决这个问题
答案 0 :(得分:10)
过滤序列相当于将一系列值转换为最多一个值的序列序列,然后将它们连接起来。也就是说,从<0,1,2,3>
过滤偶数值与将其转换为序列<<0>,<>,<2>,<>>
并连接以产生<0,2>
相同。
使用C ++ 17,这需要非常少的代码。我们将从我们自己的值和序列类型开始(您可以轻松地将std::integer_sequence
转换为value_sequence
):
template <auto >
struct value { };
template <auto... Vals>
struct value_sequence { };
我们使用自己的原因是我们可以添加运算符。像+
:
template <auto... As, auto... Bs>
constexpr value_sequence<As..., Bs...> operator+(value_sequence<As...>,
value_sequence<Bs...> )
{
return {};
}
我们将其用于连接。接下来,我们添加一个函数将单个值转换为零或一个元素的序列:
template <auto Val, class F>
constexpr auto filter_single(value<Val>, F predicate) {
if constexpr (predicate(Val)) {
return value_sequence<Val>{};
}
else {
return value_sequence<>{};
}
}
最后,我们只需要我们的顶级filter
将它们放在一起:
template <auto... Vals, class F>
constexpr auto filter(value_sequence<Vals...>, F predicate) {
return (filter_single(value<Vals>{}, predicate) + ...);
}
原始示例中的用法:
constexpr auto evens = filter(
value_sequence<0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{},
[](int i) constexpr { return i%2 == 0; });
C ++ 17有多酷!
答案 1 :(得分:5)
经过Barry的回答,我已经提出了以下答案,它合并了概念并处理了一些空序列边缘情况(Full code):
我们被允许仅在谓词是constexpr
lambda的情况下将谓词传递给函数,因为constexpr
functions中只允许使用文字类型,并且正常的自由浮动函数不是字面值类型(虽然我想你可以在lambda中包装一个)。
我们的通用过滤器函数将接受序列和谓词,并返回一个新序列。我们将使用constexpr if
来处理空序列案例(这也需要谓词上的maybe_unused
属性,因为它未被使用):
template<class INT, INT... b, class Predicate>
constexpr auto Filter(std::integer_sequence<INT, b...>, [[maybe_unused]] Predicate pred)
{
if constexpr (sizeof...(b) > 0) // non empty sequence
return concat_sequences(FilterSingle(std::integer_sequence<INT, b>{}, pred)...);
else // empty sequence case
return std::integer_sequence<INT>{};
}
Filter
函数为提供的序列中的每个元素调用FilterSingle
,并连接所有元素的结果:
template<class INT, INT a, class Predicate>
constexpr auto FilterSingle(std::integer_sequence<INT, a>, Predicate pred)
{
if constexpr (pred(a))
return std::integer_sequence<INT, a>{};
else
return std::integer_sequence<INT>{};
}
为了连接序列,基本方法是:
template<typename INT, INT... s, INT... t>
constexpr std::integer_sequence<INT,s...,t...>
concat_sequences(std::integer_sequence<INT, s...>, std::integer_sequence<INT, t...>){
return {};
}
虽然因为模板扩展我们会有很多时间超过2个序列,所以我们需要一个递归的情况:
template<typename INT, INT... s, INT... t, class... R>
constexpr auto
concat_sequences(std::integer_sequence<INT, s...>, std::integer_sequence<INT, t...>, R...){
return concat_sequences(std::integer_sequence<INT,s...,t...>{}, R{}...);
}
因为我们可能尝试将空序列连接起来(如果没有元素通过过滤器就会发生),我们需要另一个基本情况:
template<typename INT>
constexpr std::integer_sequence<INT>
concat_sequences(std::integer_sequence<INT>){
return {};
}
现在,对于我们的谓词,我们将使用constexpr
lambda。请注意,我们无需明确指定constexpr
,因为它已满足自动成为constexpr
auto is_even = [](int _in) {return _in % 2 == 0;};
所以我们的完整测试看起来像这样:
auto is_even = [](int _in) {return _in % 2 == 0;};
using expected_type = std::integer_sequence<int, 0, 2, 4, 6, 8>;
using test_type = std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>;
constexpr auto result = Filter(test_type{}, is_even);
using result_type = std::decay_t<decltype(result)>;
static_assert(std::is_same_v<expected_type, result_type>, "Integer sequences should be equal");
我的方法是重复构造和连接子序列,其中基本情况(一个序列)将返回一个空序列或者如果满足谓词则返回相同的序列。
为了编写谓词,我将利用C ++ 17 constexpr if来定义谓词。
谓词:
// base case; empty sequence
template<class INT>
constexpr auto FilterEvens(std::integer_sequence<INT>)
{
return std::integer_sequence<INT>{};
}
// base case; one element in the sequence
template<class INT, INT a>
constexpr auto FilterEvens(std::integer_sequence<INT, a>)
{
if constexpr (a % 2 == 0)
return std::integer_sequence<INT, a>{};
else
return std::integer_sequence<INT>{};
}
// recursive case
template<class INT, INT a, INT... b>
constexpr auto FilterEvens(std::integer_sequence<INT, a, b...>)
{
return concat_sequence(FilterEvens(std::integer_sequence<INT, a>{}),
FilterEvens(std::integer_sequence<INT, b...>{}));
}
连接逻辑:
template <typename INT, INT ...s, INT ...t>
constexpr auto
concat_sequence(std::integer_sequence<INT,s...>,std::integer_sequence<INT,t...>){
return std::integer_sequence<INT,s...,t...>{};
}
测试:
int main()
{
static_assert(std::is_same_v<std::integer_sequence<int, 0, 2, 4, 6, 8>, decltype(FilterEvens(std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}))>, "Integer sequences should be equal");
}
我用这种方法来解决&#34;奖金&#34;有关删除匹配位的问题:https://stackoverflow.com/a/41727221/27678
答案 2 :(得分:1)
利用元组的另一种解决方案:
#include <stdio.h>
int main(void)
{
const char* ptr = "hello";
printf("The value of ptr is %p\n", ptr);
ptr = "world";
printf("The value of ptr is %p\n", ptr);
}
像这样使用:
The address of ptr is 0000000000404000
The address of ptr is 0000000000404020
使用lambda使其工作需要c ++ 20,因此您可以通过template参数传递lambda。