Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::adjacent_difference with std::chrono time_point

Tags:

c++

c++20

chrono

Consider the following code:

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));
}

It does not compile(ugly template error message about mismatched types). When I try to switch to type in the error message(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1, 1000000000>>>) error message moves around.

My assumption is that algorithm does not work because result of substracting 2 timepoints is not a timepoint, i.e. this lines from pseudocode are conflicing.

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;
}

So I have 2 questions:

  1. Is my guess correct?
  2. What is the simplest fix? Best I can think of is ugly transform from timepoint to duration as a middlestep.

Although chrono is C++11 I am tagging this C++20 since I am open to any C++20 solutions, although I prefer them to not be ranges since they are not implemented in my compiler.

like image 371
NoSenseEtAl Avatar asked Jun 23 '20 16:06

NoSenseEtAl


Video Answer


2 Answers

My assumption is that algorithm does not work because result of substracting 2 timepoints is not a timepoint

Indeed, subtracting two time_points does not yield a time_point - it yields a duration. In <chrono>, durations and time_points form an affine space. This is similar to how you cannot add two pointers, but you can subtract two pointers - and what you get isn't a pointer, you get a ptrdiff_t.

The adjacent_difference algorithm doesn't support affine types like this because given a range [a, b, c] the output is specified to be [a, b-a, c-b]. That basically cannot work because a and b-a have different, non-convertible types.

The simplest way to do this to probably use range-v3:

zip_with(minus(), time, time | drop(1))

Produces the adjacent difference you actually want - that doesn't include the first value (the time_point) so you just get a range of durations.


There is a two-range version of transform() that I always forget about (thanks Conor). That works too:

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

That's basically the "correct" version of adjacent_difference. In C++20, this can be a little clearer:

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

You could also completely abuse adjacent_find:

std::adjacent_find(time.begin(), time.end(), [&](auto t1, auto t2){
    diffs.push_back(t2 - t1);
    return false;
});
like image 99
Barry Avatar answered Nov 15 '22 09:11

Barry


Taken from CppReference:

Computes the differences between the second and the first of each adjacent pair of elements of the range [first, last) and writes them to the range beginning at d_first + 1. An unmodified copy of *first is written to *d_first.

That last sentence is what's tripping you up.

like image 39
Marshall Clow Avatar answered Nov 15 '22 08:11

Marshall Clow