I need to have a while loop that applies a logic condition to every element of a vector.
For example, while(all elements < 3) or while(all elements != 3)
The only way I can think of is write while(vector[1]!=3 || vector[2]!=3 || ...). That would quickly grow if my vector is large.
Are there better way of doing this in C++?
See std::all_of
Assuming
std::vector<int> v;
if(std::all_of(v.cbegin(), v.cend(), [](int i){ return i < 3 }))
{
}
if(std::all_of(v.cbegin(), v.cend(), [](int i){ return i != 3 }))
{
}
Pre-C++11
struct Check
{
int d;
Check(int n) : d(n) {}
bool operator()(int n) const
{ return n < d; }
};
// Copied from above link
template< class InputIt, class UnaryPredicate >
bool all_of(InputIt first, InputIt last, UnaryPredicate p)
{
for (; first != last; ++first) {
if (!p(*first)) {
return false;
}
}
return true ;
}
if( all_of(v.begin(), v.end(), Check(3) ))
{
}
In C++11, the answer is essentially using std::all together with lambdas:
if (std::all(v.begin(),v.end(),[](const typename std::decay::type& a)
{ return a<3; })
{ 7* your stuff here */ }
In C++03, you have to workout std::all and the lambdas:
template<class Iter, Fn>
bool all(Iter i, Iter end, Fn fn)
{
for(;i!=end; ++i)
if(!fn(*i)) return false;
return true;
}
and for <3 you need an explicit class like
class is_less_than_val
{
int val;
public:
is_less_than(int value) :val(value) {}
bool operator()(int var) const { return var<val; }
};
so that you can write
if(all(v.begin(),v.end(),is_less_than_val(3))
{ /* your stuff here */ }
In case you find all the functinal machinery obscure (C++03 is not that "easy", having no substantial type deduction), and prefer a more procedural approach,
template<class Cont, class Val>
bool all_less_than(const Cont& c, const Val& v)
{
for(typename Cont::const_iterator i=c.cbegin(), i!=c.end(); ++i)
if(!(*i < v)) return false;
return true;
}
template<class Cont, class Val>
bool all_equal_to(const Cont& c, const Val& v)
{
for(typename Cont::const_iterator i=c.cbegin(), i!=c.end(); ++i)
if(!(*i == v)) return false;
return true;
}
In all the samples, whatever the code is arranged, everything boils down into a loop that breaks as false on the negation of the searched condition.
Of course, if all are numerical comparison, !(<) is >= and !(==) is !=, but since Cont and Val are template parameters, using only < and == result is less requirements for Val implementation (that can be anything, not just int)
As others have said, in C++11, there are special functions for
this. In pre-C++11, the usual idiom would have involved
std::find_if, and a comparison with the end iterator on the
results, e.g.:
struct Condition
{
// The condition here must be inversed, since we're looking for elements
// which don't meet it.
bool operator()( int value ) const { return !(value < 3); }
};
// ...
while ( std::find_if( v.begin(), v.end(), Condition() ) == v.end() ) {
// ...
}
This is such a common idiom that I continue to use it in C++11
(although often with a lambda).
Related
I have two structures with methods returning iterators on begin and end of object collection they own. Methods have different names (this may seem like a bad app architecture, but this is only a simplified model):
struct A {
std::vector<int>::iterator a_begin() { return v.begin(); }
std::vector<int>::iterator a_end() { return v.end(); }
std::vector<int> v = { 1, 2 };
};
struct B {
std::vector<float>::iterator b_begin() { return v.begin(); }
std::vector<float>::iterator b_end() { return v.end(); }
std::vector<float> v = { 1.0f, 2.0f };
};
I want to write a template function which will iterate over given object (of type A or type B) and do some job with its elements. My approach is:
template<class T>
void foo(T t) {
if constexpr (std::is_same_v<T, A>) {
for (auto it = t.a_begin(); it != t.a_end(); it++) {
// a lot of stuff
}
} else if constexpr (std::is_same_v<T, B>) {
for (auto it = t.b_begin(); it != t.b_end(); it++) {
// the same stuff
}
}
}
It looks a bit ugly for me because of for loops bodies are the same. Is there any way to improve this?
I'm taking your claim about naming and complexity at face value, so abstract and bridge.
namespace detail {
inline auto foo_begin(A& a) { return a.a_begin(); }
inline auto foo_end (A& a) { return a.a_end(); }
inline auto foo_begin(B& b) { return b.b_begin(); }
inline auto foo_end (B& b) { return b.b_end(); }
}
template<class T>
void foo(T t) {
for (auto it = detail::foo_begin(t); it != detail::foo_end(t); ++it) {
// the same stuff
}
}
The operation you wanted to vary is the range selection. So a small overload set for the types you care about should do it nicely.
If you do this often, a range adapter may be worthwhile to consider. You can write it by hand, or with C++20's std::ranges::subrange you may even leverage this overload set itself.
template<class T>
void foo(T t) {
for (auto &item : std::ranges::subrange(detail::foo_begin(t), detail::foo_end(t))) {
// the same stuff
}
}
The key concept for iterators is that two iterators define a sequence. That's all there is to it: just use a pair of iterators, rather than a container:
template <class It>
void foo(It begin, It end) {
while (begin != end) {
// a lot of stuff
++begin;
}
}
Now you can call it with a range defined by any kind of container you like:
A a;
foo(a.a_begin(), a.a_end());
B b;
foo(b.b_begin(), b.b_end());
Maybe this helps to rethink your problem.
#include <vector>
#include <algorithm>
template<typename T>
void DoStuff(const T& value)
{
};
template<typename T>
void DoAllStuffFor(const std::vector<T>& v)
{
std::for_each(v.begin(), v.end(), DoStuff<T>);
}
int main()
{
std::vector<int> v1 = { 1, 2 };
std::vector<double> v2 = { 1, 2 };
DoAllStuffFor(v1);
DoAllStuffFor(v2);
}
I want to implement a function that takes std::vector or std::array as an argument. How can the parameter list abstract from the container type?
See this example:
// how to implement this?
bool checkUniformity(container_type container)
{
for(size_t i = 1; i < container.size(); i++)
{
const auto& o1 = container[i-1];
const auto& o2 = container[i];
if(!o1.isUniform(o2))
return false;
}
return true;
}
struct Foo
{
bool isUniform(const Foo& other);
}
// I want to call it in both ways:
std::vector<Foo> vec;
std::array<Foo> arr;
bool b1 = checkUniformity(vec);
bool b2 = checkUniformity(arr);
What is the best and most readable way to do this?
Any suggestions for code improvement (style, design) are welcome as well. Thanks!
If you use iterators and ranges instead of working with the container directly, you can produce an algorithm that works with any container (including linked lists) efficiently and also with streams:
#include <list>
#include <array>
#include <vector>
#include <iterator>
template <typename T>
bool checkUniformity(T begin, T end) {
// Check for empty range
if (begin == end) return true;
// Remember last element
T last = begin;
while (++begin != end) {
if (!((*last).isUniform(*begin)))
return false;
last = begin;
}
return true;
}
template <typename T, typename F>
bool checkUniformity(T begin, T end, F pred) {
// Check for empty range
if (begin == end) return true;
// Remember last element
T last = begin;
while (++begin != end) {
if (!pred(*last, *begin))
return false;
last = begin;
}
return true;
}
struct Foo
{
bool isUniform(const Foo& other) const;
};
int main () {
// I want to call it in both ways:
std::vector<Foo> vec;
std::array<Foo, 3> arr;
std::list<Foo> list;
Foo carr [3];
bool b1 = checkUniformity(std::cbegin(vec), std::cend(vec));
bool b2 = checkUniformity(std::cbegin(arr), std::cend(arr));
bool b3 = checkUniformity(std::cbegin(list), std::cend(list));
bool b4 = checkUniformity(std::cbegin(carr), std::cend(carr));
bool b1_2 = checkUniformity(std::cbegin(vec), std::cend(vec), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
bool b2_2 = checkUniformity(std::cbegin(arr), std::cend(arr), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
bool b3_2 = checkUniformity(std::cbegin(list), std::cend(list), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
bool b4_2 = checkUniformity(std::cbegin(carr), std::cend(carr), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
}
You can also implement a second variant as shown, where you can specify the condition as a predicate (e.g. a lambda, as shown) in case you have different variants of isUniform. Having to pass two parameters for the range instead of just the container is slightly more cumbersome, but much more flexible; it also allows you to run the algorithm on a sub-range of the container.
This is the same approach as used by standard-library algorithms, such as std::find.
You want template:
template <typename container_type>
bool checkUniformity(const container_type& container)
{
for(size_t i = 1; i < container.size(); i++)
{
const auto& o1 = container[i-1];
const auto& o2 = container[i];
if(!o1.isUniform(o2))
return false;
}
return true;
}
To accept almost any container type is a good idea to use templates with template template parameters. Most of the containers in C++ accept as the fist template parameter the value type that they hold, and an allocator type, which is used to allocate memory.
To check wether the value type of the container implements a certain method, isUniform() in you case, you can use std::enable_if.
#include <iostream>
#include <vector>
#include <type_traits>
struct Foo
{
bool isUniform(const Foo&) const { return true; }
};
//Template template parameter TContainer, that accepts 2 template parameters
//the first for the value_type, the second for the allocator type
template <template <typename, typename> typename TContainer, typename TValue, typename TAllocator>
auto checkUniformity(TContainer<TValue, TAllocator>& container)
//Using `std::enable_if` to check if the result of invoking the `isUniform()` method is bool
//in case it is not bool, or the method does not exist, the `std::enable_if_t` will result
//in an error
-> std::enable_if_t
<
std::is_same_v
<
decltype(std::declval<TValue>().isUniform(std::declval<TValue>())),
bool
>,
bool
>
{
for(size_t i = 1; i < container.size(); i++)
{
const auto& o1 = container[i-1];
const auto& o2 = container[i];
if(!o1.isUniform(o2))
return false;
}
return true;
}
int main()
{
std::vector<Foo> vec(10);
std::cout << std::boolalpha << checkUniformity(vec);
return 0;
}
Note that std::array does not have an allocator type, so this method will not work with std::array.
For that you may change TContainer to a simple template type parameter, and use typename TContainer::value_type wherever you would use TValue.
I'm working on big codes for which performance matters. And one of the things I read is that raw loops should be avoided and replaced by for_each, range-based for loops, or STL algorithms etc etc. The problem is that in all (most) examples, everything looks adapted for the problem, i.e. for_each is shown with the cout routine *eye roll*.
In my case, the index inside the loop matters (unless you show me otherwise). For example, I want to create tables like this:
std::vector<double> vect1 (nmax), vect2 (nmax);
for (size_t i{0}; i < nmax; ++i) {
vect1[i] = f(i); // f is a routine defined somewhere else
vect2[i] = f(i)+2.0;
}
What I could use is the generate function with a lambda function and it would be something like this:
std::vector<double> vect1 (nmax), vect2 (nmax);
size_t count{0};
generate(vect1.begin(), vect1.end(), [&]() {return f(count++);});
count=0;
generate(vect2.begin(), vect2.end(), [&]() {return f(count++) + 2.0;});
But I’m not a big fan of that, because:
count exists until the end of the routine.
We see that with another function, I have to put back count to zero and generate another vector again. I have to track down all the count variables etc. With the for loop, I could just put it in the same loop.
With the for loop, the correspondence is seen easily. i is on the left and the right. With generate, I feel like it’s counting with a different variable on the left and the right, which means potential mistake.
I can only do count++, not ++count, which means copy of variables.
Of course, this is a simple example. But I would like to know if the generate() version is still better for this kind of things (code/performance/readability wise). Or maybe there’s a better way of doing it, and I’m open to all suggestions and comments.
Thanks!
I wrote an index range that lets me:
std::vector<double> vect1 (nmax), vect2 (nmax);
for (auto i : index_upto(nmax))
vect1[i] = f(i); // f is a routine defined somewhere else
vect2[i] = f(i)+2.0;
}
which eliminates the manual fenceposting but leaves the code otherwise unchanged.
This isn't all that hard. Write a pseudo-iterator that stores a T and returns a copy on unary *. It should support == and ++ (passing both into the stored T).
template<class T>
struct index_it {
T t;
index_it& operator++() { ++t; return *this; }
index_it operator++(int) { auto r = *this; ++*this; return r; }
friend bool operator==( index_it const& lhs, index_it const& rhs ) {
return lhs.t == rhs.t;
}
friend bool operator!=( index_it const& lhs, index_it const& rhs ) {
return lhs.t != rhs.t;
}
T operator*()const& { return t; }
T operator*()&& { return std::move(t); }
};
Next, write a range:
template<class It>
struct range {
It b, e;
It begin() const { return b; }
It end() const { return e; }
};
then compose the two.
template<class T>
using index_range = range<index_it<T>>;
template<class T>
index_range<T> make_index_range( T s, T f ) {
return {{std::move(s)}, {std::move(f)}};
}
index_range<std::size_t> index_upto( std::size_t n ) {
return make_index_range( std::size_t(0), n );
}
note that index_it is not an iterator, but works much like one. You could probably finish it and make it an input iterator; beyond that you run into problems as iterators expect backing containers.
Using a stateful lambda is not a good idea. You may be better off writing your own generate function that takes a function object receiving an iterator:
template<class ForwardIt, class Generator>
void generate_iter(ForwardIt first, ForwardIt last, Generator g) {
while (first != last) {
*first = g(first);
++first;
}
}
You can use it as follows:
generate_iter(vect1.begin(), vect1.end(), [&](const std::vector<double>::iterator& iter) {
auto count = std::distance(vect1.begin(), iter);
return f(count);
});
Demo.
We could use a mutable lambda...
#include <vector>
#include <algorithm>
double f(int x) { return x*2; }
int main()
{
constexpr int nmax = 100;
std::vector<double> vect1 (nmax), vect2 (nmax);
std::generate(vect1.begin(),
vect1.end(),
[count = int(0)]() mutable { return f(count++); });
std::generate(vect2.begin(),
vect2.end(),
[count = int(0)]() mutable { return f(count++) + 2.0; });
}
Another option (uses c++17 for template argument deduction):
template<class F>
struct counted_function
{
constexpr counted_function(F f, int start = 0, int step = 1)
: f(f)
, counter(start)
, step(step) {}
decltype(auto) operator()() {
return f(counter++);
}
F f;
int counter;
int step;
};
used as:
std::generate(vect2.begin(),
vect2.end(),
counted_function([](auto x) { return f(x) + 2.0; }));
And finally, just for fun, could write this:
generate(vect2).invoking(f).with(every<int>::from(0).to(nmax - 1));
...if we had written something like this...
#include <vector>
#include <algorithm>
#include <iterator>
double f(int x) { return x*2; }
template<class T> struct value_iter
{
using value_type = T;
using difference_type = T;
using reference = T&;
using pointer = T*;
using iterator_category = std::forward_iterator_tag;
friend bool operator==(value_iter l, value_iter r)
{
return l.current == r.current;
}
friend bool operator!=(value_iter l, value_iter r)
{
return !(l == r);
}
T const& operator*() const& { return current; }
value_iter& operator++() { ++current; return *this; }
T current;
};
template<class T> struct every
{
struct from_thing
{
T from;
struct to_thing
{
auto begin() const { return value_iter<T> { from };}
auto end() const { return value_iter<T> { to+1 };}
T from, to;
};
auto to(T x) { return to_thing { from, x }; }
};
static constexpr auto from(T start)
{
return from_thing { start };
}
};
template<class F>
struct counted_function
{
constexpr counted_function(F f, int start = 0, int step = 1)
: f(f)
, counter(start)
, step(step) {}
decltype(auto) operator()() {
return f(counter++);
}
F f;
int counter;
int step;
};
template <class Container> struct generate
{
generate(Container& c) : c(c) {}
template<class F>
struct invoking_thing
{
template<class Thing>
auto with(Thing thing)
{
using std::begin;
using std::end;
std::copy(begin(thing), end(thing), begin(c));
return c;
}
F f;
Container& c;
};
template<class F>
auto invoking(F f) { return invoking_thing<F>{f, c}; }
Container& c;
};
int main()
{
constexpr int nmax = 100;
std::vector<double> vect2 (nmax);
generate(vect2).invoking(f).with(every<int>::from(0).to(nmax - 1));
}
With range-v3, it would be something like:
auto vect1 = ranges::view::ints(0, nmax) | ranges::view::transform(f);
auto vect2 = ranges::view::ints(0, nmax) | ranges::view::transform(f2);
// or auto vect2 = vect1 | ranges::view::transform([](double d){ return d + 2.; });
Demo
I have a class which contains some vectors (minimal code here):
class Foo
{
public:
Foo() = default;
private:
void DoStuff();
std::vector<int>& mBarInt;
std::vector<double> mBarDouble;
int do_something_with_int;
double do_something_with_double;
};
On my DoStuff() method I have:
void Foo::DoStuff()
{
for(auto &val : mBarInt)
{
// do something here...
do_something_with_int = val + ....
}
}
I would like to do something like this, accessing the two vectors on the same for, but (see below after code) ...
for(int i = 0 ; i < mBarInt.size() ; i++)
{
// do something here...
do_something_with_int = mBarInt[i] + ....
do_something_with_double = mBarDouble[i] + .... // I know mBarDouble has the same size has mBarInt
}
... keeping the same c++11 syntax with the: for(auto &val : mBarInt)
Something like this below:
void Foo::DoStuff()
{
for(auto &val1, auto val2 : mBarInt, mBarDouble) //?????<---- How can I do this?
{
// do something with val1 and val2, which I know that will point to different vectors, but on the same index.
}
}
template<class It>
struct indexing_iterator {
It it = {};
using value_type = It;
using reference = value_type;
It operator*() const { return it; }
indexing_iterator& operator++() { ++it; return *this; }
friend bool operator==( indexing_iterator const& lhs, indexing_iterator const& rhs ) {
return lhs.it == rhs.it;
}
friend bool operator!=( indexing_iterator const& lhs, indexing_iterator const& rhs ) {
return !(lhs==rhs);
}
};
template<class F, class It_in>
struct transform_iterator_t:indexing_iterator<It_in> {
F f = {};
using reference = decltype(std::declval<F&>()( *std::declval<It_in&>() ) );
using value_type = std::decay_t<reference>;
using pointer = value_type*;
reference operator*() { return f(*this->it); }
transform_iterator_t( F f_in, It_in it_in ):
indexing_iterator<It_in>{it_in},
f(f_in)
{}
};
template<class F, class It_in>
transform_iterator_t<F,It_in> transform_iterator(
F f, It_in it
) {
return {f, it};
}
indexing_iterator<std::size_t> index( std::size_t n ) {
return {n};
}
template<class It>
struct range_t {
It b,e;
It begin() const { return b; }
It end() const { return b; }
};
template<class It>
range_t<It> range( It b, It e ) { return {b,e}; }
auto range_upto( std::size_t n ) {
return range( index(0), index(n) );
}
template<class C>
auto to_range( C&& c ) {
using std::begin; using std::end;
return range( begin(c), end(c) );
}
template<class It, class F>
range_t< transform_iterator< F, It > >
transform_range( F f, range_t<It> range ) {
return {
transform_iterator( f, range.begin() ),
transform_iterator( f, range.end() )
};
}
template<class F>
auto generator_range( F f, std::size_t count ) {
return transform_range( f, range_upto(count) );
}
template<class C1, class C2>
auto zip_over( C1& c1, C2& c2 ) {
return generator_range(
[&](std::size_t i) {
return std::tie( c1[i], c2[i] );
},
(std::min)(c1.size(), c2.size())
);
}
then in C++17 we have
void Foo::DoStuff() {
for(auto&&[val1, val2] : zip_over(mBarInt, mBarDouble)) {
}
}
or you can manually unpack the tuple in C++14.
Seems like a lot of work.
There are implementations of zip and the like in boost and other libraries, including Rangev3.
Code not tested. Design should be sound.
There is probably a shorter way to do it more directly, but I find generator via transform and indexing, and zip via generator, easier to understand and write with fewer bugs.
An indexing_iterator takes an incrementable comparable object, and iterates over it. The It can be an integer-like or an interator.
A transform_iterator takes an iterator and a transformation, and returns the transformation on each element.
Neither are full iterators; I skipped the boilerplate. But they are enough for for(:) loops.
A range holds 2 iterators and exposes them to for(:).
An indexing_iterator<std::size_t> is otherwise known as a counting iterator.
A generator iterator is a transformation of a counting iterator. It takes a function that takes an index, and generates an element for that index.
We zip over two random access containers by generating a generator iterator that calls operator[] on both, then returns a tuple. Fancier zipping handles non-random access containers.
The zip of two containers then lets you iterate in parallel over two different containers.
I then use C++17 structured bindings in the for(:) statement to unpack them into two different variables. We could instead just manually unpack as the first 2 statements in our for loop.
You can use boost::make_iterator_range and boost::zip_iterator like this:
std::vector<int> ints{1,2,3,4,5};
std::vector<double> doubles{1,2,3,4,5.1};
int do_something_with_int{0};
double do_something_with_double{0};
for (const auto& ref :
boost::make_iterator_range(
boost::make_zip_iterator(boost::make_tuple(ints.cbegin(), doubles.cbegin())),
boost::make_zip_iterator(boost::make_tuple(ints.cend(), doubles.cend()))))
{
do_something_with_int += boost::get<0>(ref);
do_something_with_double+= boost::get<1>(ref);
}
Live example.
Is there a way to write a declarative style for loop in C++14
for(int i = 0; i < 10; i+=2) {
// ... some code
}
The closest I have found is using boost is
for(auto i : irange(1,10,2)){
// .... some code
}
Is there a c++14/17 standards way of achieving the same effect?I was trying std::make_integer_sequence() as a possible starting point, but couldn't quite figure it out.
There is not a standard way of iterating over a numerical range as you desire. C++14 however allows you to create your own utilities very easily.
Approach one: numerical range.
Here's an unoptimized quickly-hacked-together example:
template <typename T>
struct num_range_itr
{
T _value, _step;
num_range_itr(T value, T step)
: _value{value}, _step{step}
{
}
auto operator*() const { return _value; }
auto operator++() { _value += _step; }
auto operator==(const num_range_itr& rhs) const { return _value == rhs._value; }
auto operator!=(const num_range_itr& rhs) const { return !(*this == rhs); }
};
template <typename T>
struct num_range
{
T _start, _end, _step;
num_range(T start, T end, T step)
: _start{start}, _end{end}, _step{step}
{
}
auto begin() const { return num_range_itr<T>{_start, _step}; }
auto end() const { return num_range_itr<T>{_end, _step}; }
};
template <typename T>
auto make_range(T start, T end, T step = 1)
{
return num_range<T>{start, end, step};
}
The code above can be used as follows:
for(auto i : make_range(0, 10, 2))
{
std::cout << i << " ";
}
// Prints: 0 2 4 6 8
Approach two: higher-order function.
Here's an unoptimized quickly-hacked-together example:
template <typename T, typename TF>
auto for_range(T start, T end, T step, TF f)
{
for(auto i = start; i < end; i += step)
{
f(i);
}
return f;
}
The code above can be used as follows:
for_range(0, 10, 2, [](auto i){ std::cout << i << " "; });
// Prints: 0 2 4 6 8
The code can be found on wandbox.
If you want to use these utilities in real projects, you need to carefully write them in order to support multiple types safely, avoid unintentional signed/unsigned conversions, avoid invalid ranges, and make sure they get optimized away.
std::integer_sequence will help you only if you know the size at the compilation time. I personally hate pre C++11 for-loop. It is quite easy to make a mistake by writing something like:
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++i) // very hard to spot
I use my little helper seq_t class, which I believe exactly what you need. Please note typename std::enable_if. It is to make sure it will be only used for integral types (int, uint8_t, long long etc.), but you can't use it for double or float. You can use it in three different ways:
for (int i : seq(1, 10)) // [1, 10), step 1
for (int i : seq(1, 10, 2)) // [1, 10), step 2
for (int i : seq(10)) // [0, 10), step 1
Here is my code:
#include <type_traits>
template<typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type >
class seq_t
{
T m_begin;
T m_end;
T m_step;
public:
class iterator
{
T m_curr;
T m_step;
constexpr iterator(T curr, T step)
: m_curr(curr), m_step(step)
{
}
constexpr iterator(T end)
: m_curr(end), m_step(0)
{
}
friend class seq_t;
public:
constexpr iterator& operator++()
{
m_curr += m_step;
return *this;
}
constexpr T operator*() const noexcept
{
return m_curr;
}
constexpr bool operator!=(const iterator& rhs) const noexcept
{
return this->m_curr != rhs.m_curr;
}
};
constexpr iterator begin(void) const noexcept
{
return iterator(m_begin, m_step);
}
constexpr iterator end(void) const noexcept
{
return iterator(m_end, m_step);
}
constexpr seq_t(T begin, T end, T step) noexcept
: m_begin(begin), m_end(end), m_step(step)
{
}
};
template<typename T>
inline seq_t<T>
constexpr seq(T begin, T end, T step = 1) noexcept
{
return seq_t<T>(begin, end, step);
}
template<typename T>
inline seq_t<T>
constexpr seq(T end) noexcept
{
return seq_t<T>(T{0}, end, 1);
}