std::adjacent_difference与std::chrono time_point的结合使用

5
考虑以下代码:
int main()
{
    std::vector<std::chrono::steady_clock::time_point> time;
    time.push_back(std::chrono::steady_clock::now());
    std::this_thread::sleep_for(std::chrono::milliseconds(4));
    time.push_back(std::chrono::steady_clock::now());
    std::this_thread::sleep_for(std::chrono::milliseconds(7));
    time.push_back(std::chrono::steady_clock::now());
    std::vector<std::chrono::duration<double>> diffs;
    std::adjacent_difference(time.begin(),time.end(),std::back_inserter(diffs));
}

它无法编译(关于类型不匹配的丑陋模板错误消息)。 当我尝试切换到错误消息中的类型时(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1, 1000000000>>>),错误消息会移动。
我的假设是算法不起作用,因为减去两个时间点的结果不是时间点,即伪代码中的这些行存在冲突。
template<class InputIt, class OutputIt>
constexpr // since C++20
OutputIt adjacent_difference(InputIt first, InputIt last, 
                             OutputIt d_first)
{
    if (first == last) return d_first;
 
    typedef typename std::iterator_traits<InputIt>::value_type value_t;
    value_t acc = *first;  
    *d_first = acc; // <-----------------------------------------------------  1
    while (++first != last) {
        value_t val = *first;
        *++d_first = val - std::move(acc); // std::move since C++20  <-------- 2
        acc = std::move(val);
    }
    return ++d_first;
}

所以我有两个问题:

  1. 我的猜测正确吗?
  2. 最简单的修复方法是什么?我能想到的最佳解决方法是从时间点转换为持续时间作为中间步骤,但是它不太美观。

尽管chrono是C++11,但由于我对任何C++20的解决方案都持开放态度,因此我将其标记为C++20。尽管我的编译器没有实现ranges,但我更喜欢它们不要成为ranges。

2个回答

4
我的假设是算法无法正常工作,因为两个时间点之间的差不是一个时间点。实际上,减去两个 time_point 值并不会得到一个 time_point,而是一个 duration。在 中,duration 和 time_point 形成一个仿射空间。这类似于您不能将两个指针相加,但可以将两个指针相减,但得到的不是指针而是 ptrdiff_t。
adjacent_difference 算法不支持这样的仿射类型,因为给定范围 [a, b, c],输出被指定为 [a,b-a,c-b]。这基本上行不通,因为 a 和 b-a 有不同的、不可转换的类型。
最简单的方法可能是使用 range-v3:
zip_with(minus(), time, time | drop(1))

产生您实际想要的相邻差异 - 不包括第一个值(time_point),因此您只会获得一系列durations。


还有一个我总是忘记的双范围版本的transform()(感谢 Conor)。那也可以:

std::transform(time.begin(), std::prev(time.end()), std::next(time.begin()),
    std::back_inserter(diffs), std::minus());

这基本上是adjacent_difference的“正确”版本。在C++20中,可以更清晰地表示:

std::ranges::transform(time, time | std::views::drop(1),
    std::back_inserter(diffs), std::minus());

你也可以彻底滥用adjacent_find函数:

std::adjacent_find(time.begin(), time.end(), [&](auto t1, auto t2){
    diffs.push_back(t2 - t1);
    return false;
});

range-v3 是指 Eric 在 C++20 中没有理解它,我猜测是这样吗?或者你是在提到它,因为现在它已经可用了。 - NoSenseEtAl
1
@NoSenseEtAl 在C++20中没有zip/zip_with函数。 - Barry
1
真糟糕,@Barry!我正在写我的答案时,你自己发了一个(更好的)答案。 :-) - Marshall Clow
1
@NoSenseEtAl ... 但是有二进制的 transform - Barry

2
CppReference 中得知:
计算范围[first, last)内相邻一对元素的第二个和第一个之间的差,并将它们写入从d_first + 1开始的范围。 * first 的未修改副本写入* d_first
最后一句话是让你迷惑的。

这实际上非常惊人,我认为我从未见过任何类型在std ::中不能与所有算法一起使用(不是指迭代器要求或仅移动类型,而是指类型本身的操作有问题)。 - NoSenseEtAl
2
@Barry 指出,原始指针是另一种在 adjacent_difference 中存在问题的类型(因为两个指针的差异是 ptrdiff_t 类型)。 - Marshall Clow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接