7

I am try to create an iterable type which receives a template argument of a specific duration type, say std::seconds , std::hours , etc., and I want it to receive as argument 2 values that represent time_points of the specified duration, and be able to use such a construct in a range based for loop by increasing the current time_point by a unit of that duration or maybe of a specified duration, something like the following:

DateRange<std::seconds> dr(now() , 50);
for(auto d : dr){
 // do something at unit time
}

I have tried to implement it this way

   using namespace std::chrono;
   template<typename Duration , typename Clock_t = high_resolution_clock, 
   typename Time_type = time_point<Clock_t, typename Duration> , typename 
   Time_pointer = Time_type* >
 class DateRange {
   using Time_type_t = typename Time_type::duration;
 public:
   DateRange(Time_type_t start, Time_type_t end) :
    m_begin(start),
    m_end(end)
   {

   }
   DateRange(Time_type_t end):
    m_begin(Clock_t::now())

   {

   }
   Time_pointer begin(){
    return &m_begin;
   }
   Time_pointer end() {
    return &m_end;
    }

    Time_pointer operator++(){
    present +=Duration(1);
    return present_point;
   }

 Time_type operator*(){
    return present;
  }

private:
Time_type m_begin;
Time_type m_end;
Time_type present;
Time_pointer present_point = &present;
Clock_t l_clock;
};
int main()
{
DateRange<seconds> dr(40s);
dr.operator++();
    std::cout << (*dr).time_since_epoch().count();
 }

'std::chrono::time_point::time_point(std::chrono::time_point &&)': cannot convert argument 1 from 'std::chrono::steady_clock::time_point' to 'const _Duration &' DateRange at line 19

2 Answers2

3

Range-based for loops (for ( range_declaration : range_expression ) loop_statement) are syntactic sugar for (in your case) this:

{
    auto && __range = range_expression ;
    auto __begin = __range.begin();
    auto __end = __range.end();
    for ( ; __begin != __end; ++__begin)
    {
        range_declaration = *__begin;
        loop_statement
    }
} 

Without concerning ourselves with the finer details of this (or how it changed throughout C++ versions), this explains why your code currently cannot work. You are attempting to do this:

  • auto && __range = myDateRange; - Ok, our range is a DateRange. This is fine.

  • auto __begin = __range.begin();
    auto __end = __range.end();
    So __begin and __end are now Time_type*... This is already looking bad.

  • Now the loop will increment __begin, but it is a TimeType* that does not point into an array. Dereferencing the incremented __begin (as done in the next statement) will thus be Undefined Behavior. Note how operator++ of the range_expression is never called.

Regardless of if you fix the compiler error (and missing initialization in DateRange(Time_type_t end)), this approach will not work. You need an iterator class that keeps a reference to your DateRange. This iterator is returned by begin() and end() and has itself an operator++() and operator*() (which would return an appropriate time_point).

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
2

The value you return from begin needs to eventually be equal to the value you return from end by some amount of ++. Currently whether this is possible is unspecified, as you return pointers to distinct objects.

You probably need a separate iterator type.

template<typename Duration, typename Clock = high_resolution_clock>
class DateRange {
   using Time_type = time_point<Clock, Duration>;

   class iterator {
     iterator & operator++(){
       present += Duration(1);
       return *this;
     }

     iterator operator++(int){
       iterator res = *this;
       ++res;
       return res;
     }

     Time_type * operator->(){
       return &present;
     }

     Time_type operator*(){
       return present;
     }

     Time_type present;
   };

   Time_type m_begin;
   Time_type m_end;
public:
  DateRange(Time_type_t start, Time_type_t end) :
    m_begin(start),
    m_end(end)
    {}
  DateRange(Time_type_t end) :
    m_begin(Clock::now()),
    m_end(end)
    {}
  iterator begin(){
    return m_begin;
  }
  iterator end() {
    return m_end;
  }
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • _Technically_ it's likely that a single `++` makes the pointers compare equal - they are adjacent members after all. Not that this would be the intended behavior. – Max Langhof Nov 02 '18 at 10:04
  • @MaxLanghof they aren't pointers into the same array, so are outside the domain of `==` – Caleth Nov 02 '18 at 10:06
  • That is [only needed for relational operators](https://stackoverflow.com/questions/9086372/how-to-compare-pointers). I'm not saying that it's guaranteed to be UB-free (padding could theoretically screw this if `Time_type` was e.g. a `char[3]`), but the program isn't ill-formed for checking the equality of `Time_type` pointers, same array or not. – Max Langhof Nov 02 '18 at 10:07
  • @MaxLanghof [`[expr.eq]/3.1`](http://eel.is/c++draft/expr.eq#3.1) "If one pointer represents the address of a complete object, and another pointer represents the address one past the last element of a different complete object, the result of the comparison is unspecified." – Caleth Nov 02 '18 at 10:11
  • Ok, fair point. Still, it's "just" unspecified, and there is no mention of "same array" or similar. This is the kind of semantic nitpicking I wanted to avoid in my answer, but +1 for seeing it through :) – Max Langhof Nov 02 '18 at 10:13
  • I think iterators are fundamentally pointers and .begin() and .end() show return dereferencable pointers – Ogunleye Ayowale Pius Nov 02 '18 at 12:26
  • @OgunleyeAyowalePius Pointers to objects can be `begin` Iterators. Pointers to *elements of an array* can be both `begin` and `end` Iterators. One past the end of an array (or one past an object) is an `end` Iterator. – Caleth Nov 02 '18 at 12:36
  • @OgunleyeAyowalePius In general, the result of `end()` is *not* dereferencable. It certainly isn't a pointer to an element in a different array than `begin()` – Caleth Nov 02 '18 at 12:39
  • 1
    @OgunleyeAyowalePius "I think iterators are fundamentally pointers" You have it backwards. All pointers are iterators (although many are not *useful* as such). Not all iterators are pointers. E.g. [`std::istream_iterator`](https://en.cppreference.com/w/cpp/iterator/istream_iterator) is a **class template** – Caleth Nov 02 '18 at 12:42