end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = &seq[0]; const char * end = &seq[std::size(seq)]; print(begin, end); } 4 Consider this C++ program demonstrating the use of iterators.
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = &seq[0]; const char * end = &seq[std::size(seq)]; print(begin, end); } 5 We have a sequence of characters.
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = &seq[0]; const char * end = &seq[std::size(seq)]; print(begin, end); } 6 We compute the address to the beginning and to the end of the sequence.
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = &seq[0]; const char * end = &seq[std::size(seq)]; print(begin, end); } 12 We iterate through every address from begin to, but not including, the end.
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = &seq[0]; const char * end = &seq[std::size(seq)]; print(begin, end); } 14 For each step, the iterator is dereferenced and the value is used as an argument to std::putchar
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = &seq[0]; const char * end = &seq[std::size(seq)]; print(begin, end); } 16 The output of this program is... of course...
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = &seq[0]; const char * end = &seq[std::size(seq)]; print(begin, end); } 21 In the C++ standard library, there are helper functions to find the begin and end of a sequence.
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = std::cbegin(seq); const char * end = std::cend(seq); print(begin, end); } 30 These helpers are generic (type independent). The type of the iterator depends on the type of the sequence. We can use type deduction to get the correct type for our iterators.
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; const char * begin = std::cbegin(seq); const char * end = std::cend(seq); print(begin, end); } 32 De r pi , I wa t y o t p e s ha n e "ri t d e" of s i li on...
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin = std::cbegin(seq); auto end = std::cend(seq); print(begin, end); } 34
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin = std::cbegin(seq); auto end = std::cend(seq); print(begin, end); } 35
end) { for (const char * it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin = std::cbegin(seq); auto end = std::cend(seq); print(begin, end); } 36
end) { for (auto it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin = std::cbegin(seq); auto end = std::cend(seq); print(begin, end); } 37
end) { for (auto it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin = std::cbegin(seq); auto end = std::cend(seq); print(begin, end); } 38
end) { for (auto it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin = std::cbegin(seq); auto end = std::cend(seq); print(begin, end); } 39 Hello .. 3 type deduction and auto
end) { for (auto it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin = std::cbegin(seq); auto end = std::cend(seq); print(begin, end); } 40
end) { for (auto it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin = std::cbegin(seq); auto end = std::cend(seq); print(begin, end); } 41
end) { for (auto it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin{std::cbegin(seq)}; auto end = std::cend(seq); print(begin, end); } 42
end) { for (auto it = begin; it != end; ++it) std::putchar(*it); } int main() { const char seq[] = {'H', 'e', 'l', 'l', 'o'}; auto begin{std::cbegin(seq)}; auto end = std::cend(seq); print(begin, end); } 43
end) { for (auto it{begin}; it != end; ++it) std::putchar(*it); } int main() { const char seq[]{'H', 'e', 'l', 'l', 'o'}; auto begin{std::cbegin(seq)}; auto end{std::cend(seq)}; print(begin, end); } 50 Many consider {}-initialization (aka brace initializers) to be superior. They give some extra type checking and other features. (but looks quite ugly, IMHO)
end) { for (auto it{begin}; it != end; ++it) std::putchar(*it); } int main() { const char seq[]{'H', 'e', 'l', 'l', 'o'}; auto begin{std::cbegin(seq)}; auto end{std::cend(seq)}; print(begin, end); } 53 Let's clean up a bit...
end) { for (auto it{begin}; it != end; ++it) std::putchar(*it); } int main() { const char seq[]{'H', 'e', 'l', 'l', 'o'}; print(std::cbegin(seq), std::cend(seq)); } 61 This is a typical imperative for-loop. It is also common to use the declarative std::for_each algorithm for simple loops like this.
end) { std::for_each(begin, end, std::putchar); } int main() { const char seq[]{'H', 'e', 'l', 'l', 'o'}; print(std::cbegin(seq), std::cend(seq)); } 67 Passing iterator pairs is used all over in C++, in particular in the C++ standard library. We may also use a container...
std::putchar); } int main() { std::vector<char> seq{'e', 'l', 'l', 'H', 'o'}; print(seq); } 94 Perhaps we need to rearrange the elements before printing out.
std::putchar); } int main() { std::vector<char> seq{'e', 'l', 'l', 'H', 'o'}; print(seq); } 95 Then we might want to pass by value so that we get a copy of the container.
std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq); } 106 Here we have some superfluous punctuation characters that needs to be filtered out.
::ispunct), std::end(seq)); std::for_each(std::cbegin(seq), std::cend(seq), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq); } 114 At this abstraction level it is easy to discover optimization opportunities. Here, it looks like we should filter out the elements first and *then* sort the container.
std::sort(std::begin(seq), std::end(seq)); std::for_each(std::cbegin(seq), std::cend(seq), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq); } 118 Another, now, obvious optimization is to let sort and for_each work on the subsequence after remove_if, making the erase step obsolete.
std::end(seq), ::ispunct); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq); } 136 As well as values, you can also pass function objects. Now we will let the caller provide the filter predicate.
newend = std::remove_if(std::begin(seq), std::end(seq), filter); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq, ::ispunct); } 145 Now we can pass in quite advanced filters. Let us create an old-school function object (aka functor).
Filter filter) { auto newend = std::remove_if(std::begin(seq), std::end(seq), filter); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), typename Seq::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq, [](char ch) { return !std::isalpha(ch); }); } 183 typename is needed here to help the compiler to look for a type, instead of a value.
Filter filter) { auto newend = std::remove_if(std::begin(seq), std::end(seq), filter); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), typename Seq::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq, [](char ch) { return !std::isalpha(ch); }); } 187 Generic code is nice, but often you will need to specify certain requirements for the template arguments. Types are known at compile time, so we can use type traits and static assert to reason about template arguments.
Filter filter) { static_assert(std::is_integral<typename Seq::value_type>::value); static_assert(std::is_invocable<Filter, int>::value); auto newend = std::remove_if(std::begin(seq), std::end(seq), filter); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), typename Seq::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq, [](char ch) { return !std::isalpha(ch); }); } 194 With enable_if we can take this idea further and tell the compiler that if certain requirements are not met, then a particular template should not even be considered for instantiation.
TODO: also check that sequence is sortable return std::is_integral<typename Seq::value_type>::value; } template <typename Filter> concept bool MyFilterConcept() { return std::is_invocable<Filter, int>::value; } template <MySeqConcept Seq, MyFilterConcept Filter> void print(Seq seq, Filter filter) { auto newend = std::remove_if(std::begin(seq), std::end(seq), filter); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), typename Seq::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq, [](char ch) { return !std::isalpha(ch); }); } 216 I am now going to show something that probably will not make it into C++20, and maybe never... However, with the current version of gcc (8.2.0) it is possible to play with the idea of using concepts instead of types in a function declaration. (The effect is that you are really declaring a template.)
TODO: also check that sequence is sortable return std::is_integral<typename Seq::value_type>::value; } template <typename Filter> concept bool MyFilterConcept() { return std::is_invocable<Filter, int>::value; } template <MySeqConcept Seq, MyFilterConcept Filter> void print(Seq seq, Filter filter) { auto newend = std::remove_if(std::begin(seq), std::end(seq), filter); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), typename Seq::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq, [](char ch) { return !std::isalpha(ch); }); } 217 First I need to make a small change in the iterator pair for the for_each since the Seq type will soon be replaced by a concept.
TODO: also check that sequence is sortable return std::is_integral<typename Seq::value_type>::value; } template <typename Filter> concept bool MyFilterConcept() { return std::is_invocable<Filter, int>::value; } void print(MySeqConcept seq, MyFilterConcept filter) { auto newend = std::remove_if(std::begin(seq), std::end(seq), filter); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), typename decltype(seq)::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq, [](char ch) { return !std::isalpha(ch); }); } 225 Now that we are using the concept names in the function declaration, we can go further and rename them.
std::remove_if(std::begin(seq), std::end(seq), [](char ch) { return !std::isalpha(ch); }); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq); } 244 The key thing here is the & to avoid a copy operation. We pass the sequence by a so called lvalue reference. Since we are not using the sequence after the call to print, this might be an acceptable solution.
std::remove_if(std::begin(seq), std::end(seq), [](char ch) { return !std::isalpha(ch); }); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(seq); } 248 It would be nice to express ownership when passing references like this. Who owns the content of the object? With rvalue references and move semantics you can in our example express transfer of ownership with && and a std::move.
std::remove_if(std::begin(seq), std::end(seq), [](char ch) { return !std::isalpha(ch); }); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(std::move(seq)); } 252 It is important to realize that std::move is just a cast from lvalue to rvalue.
std::remove_if(std::begin(seq), std::end(seq), [](char ch) { return !std::isalpha(ch); }); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } int main() { std::vector<char> seq{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; print(std::move(seq)); } 256 Now we will show how rvalue references can bind to objects without a name, eg a return value from a function.
std::remove_if(std::begin(seq), std::end(seq), [](char ch) { return !std::isalpha(ch); }); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } std::vector<char> greeting() { return std::vector<char>{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; } int main() { print(greeting()); } 265 Due to return value optimization, copy elision, rvalues and move semantics, nothing will be copied here. All the elements (and the ownership) are just moved into the print function.
std::remove_if(std::begin(seq), std::end(seq), [](char ch) { return !std::isalpha(ch); }); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } std::vector<char> greeting() { return std::vector<char>{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; } int main() { print(greeting()); } 269 std::vector has a move constructor, so here pass by value is just moving elements if we give it an rvalue.
std::end(seq), [](char ch) { return !std::isalpha(ch); }); std::sort(std::begin(seq), newend); std::for_each(std::cbegin(seq), std::vector<char>::const_iterator(newend), std::putchar); } std::vector<char> greeting() { return std::vector<char>{'e', '&', '.', 'l', 'l', '{', 'H', 'o'}; } int main() { print(greeting()); } 275 When planning this presentation, I promised myself that I would show structural binding and if statement with initialization. Although a small detour, this is the time to show it.
std::size(seq); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; print(seq); } 289 No problem, just apply reverse iterators to get it right.
std::size(seq); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; print(seq); } 293 Let's write our very own putchar and put it in our very own namespace.
std::cout << ch << std::flush; } } std::size_t print(std::vector<char> seq) { std::for_each(std::crbegin(seq), std::crend(seq), my::putchar); return std::size(seq); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; print(seq); } 311 We will soon show how to run code asynchronously. But first we show an alternative way to invoke a function.
std::cout << ch << std::flush; } } std::size_t print(std::vector<char> seq) { std::for_each(std::crbegin(seq), std::crend(seq), my::putchar); return std::size(seq); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; auto f = std::async(print, seq); auto n = f.get(); std::cout << n; } 322 std::async returns a future that holds a value that we can retrieve later. Notice how invoking and getting the result is now two separate statements, this allow us to something while it is executing.
std::cout << ch << std::flush; } } std::size_t print(std::vector<char> seq) { std::for_each(std::crbegin(seq), std::crend(seq), my::putchar); return std::size(seq); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; auto f = std::async(print, seq); for (int i = 10; i; --i) my::putchar('.'); auto n = f.get(); std::cout << n; } 327 .H.el.l..o.....5 .. 31 this what I often get on my machine... std::async promise and future
std::cout << ch << std::flush; } } std::size_t print(std::vector<char> seq) { std::for_each(std::crbegin(seq), std::crend(seq), my::putchar); return std::size(seq); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; auto f = std::async(print, seq); for (int i = 10; i; --i) my::putchar('.'); auto n = f.get(); std::cout << n; } 329 The default launch policy depends on your compiler/runtime. By explicitly specify the launch policy you can for example defer execution until you call the get() function. This can be useful, in particular when debugging.
std::cout << ch << std::flush; } } std::size_t print(std::vector<char> seq) { std::for_each(std::crbegin(seq), std::crend(seq), my::putchar); return std::size(seq); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; auto f = std::async(std::launch::deferred, print, seq); for (int i = 10; i; --i) my::putchar('.'); auto n = f.get(); std::cout << n; } 335 Or you can specify that you want threads to be created and started.
std::cout << ch << std::flush; } } std::size_t print(std::vector<char> seq) { std::for_each(std::crbegin(seq), std::crend(seq), my::putchar); return std::size(seq); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; auto f = std::async(std::launch::async, print, seq); for (int i = 10; i; --i) my::putchar('.'); auto n = f.get(); std::cout << n; } 341 Ok, I just can not resist... let's have some fun with overloading the multiplication operator and create a repeat function. (Don't do this in your codebase...)
std::cout << ch << std::flush; } } std::size_t print(std::vector<char> seq) { std::for_each(std::crbegin(seq), std::crend(seq), my::putchar); return std::size(seq); } template <typename Func> void operator*(int n, Func f) { while (n--) f(); } int main() { std::vector<char> seq{'o', 'l', 'l', 'e', 'H'}; auto f = std::async(std::launch::async, print, seq); 10 * []{ my::putchar('.'); }; auto n = f.get(); std::cout << n; } 349 I am now going to show user-defined literals, but first we clean up the code.
bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::for_each(&s[bi], &s[ei], std::putchar); } 393 After finding the beginning and the end, we can create a view into the string...
bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); std::for_each(std::cbegin(sv), std::cend(sv), std::putchar); } 399 And since this is just a trivial pass from begin to end, we can also use the very cute range-for.
bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 402
bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 403
bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 405
bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 406 The C++ standard library also comes with predefined string literals.
bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 407
= "................Hello.........."; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 408
= "................Hello.........."; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 409
= "................Hello.........."s; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 410
= "................Hello.........."s; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 411
= "................Hello.........."s; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 412
= "................Hello.........."s; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 413
= "................Hello.........."s; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 414 There is also a string_view literal...
= "................Hello.........."s; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 415
= "................Hello.........."sv; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 416
= "................Hello.........."sv; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 417
= "................Hello.........."sv; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 419
= "................Hello.........."sv; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 420 Notice that most of this could be computed in compile time. Indeed, with constexpr you can force the compiler to compute stuff if possible.
= "................Hello.........."sv; std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); std::size_t len = ei - bi; std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 421
s = "................Hello.........."sv; constexpr std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); constexpr std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); constexpr std::size_t len = ei - bi; constexpr std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 422
s = "................Hello.........."sv; constexpr std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); constexpr std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); constexpr std::size_t len = ei - bi; constexpr std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 423
s = "................Hello.........."sv; constexpr std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); constexpr std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); constexpr std::size_t len = ei - bi; constexpr std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 425
s = "................Hello.........."sv; constexpr std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); constexpr std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); constexpr std::size_t len = ei - bi; constexpr std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 426 And last, but not least...
s = "................Hello.........."sv; constexpr std::size_t bi = std::min(s.find_first_not_of('.'), std::size(s)); constexpr std::size_t ei = std::min(s.find_first_of('.',bi), std::size(s)); constexpr std::size_t len = ei - bi; constexpr std::string_view sv(&s[bi], len); for (char c : sv) std::putchar(c); } 427
and auto 4. {} initialization (aka brace initialization) 5. std::for_each 6. user-defined container and pass by const ref 7. std::array 8. std::vector 9. std::sort and pass by value (copy) 10. erase-remove idiom 11. algorithms and high level optimization 12. std::function 13. function objects (aka functors) 14. lambda expression and generalized lambda capture 15. wrap functions with lambda expressions 16. function template and dependent names 17. type traits and static assert 18. enable_if 19. concepts and requires (C++20?) 20. concepts as template parameters (C++20?) 21. concepts in function declaration (C++23?) 22. pass by lvalue reference 23. pass by rvalue reference and std::move 24. return value optimization and copy elision 25. the std::vector move constructor 26. std::tuple, structural binding and if statement with initializer 27. reverse iterators 28. namespace and my::putchar 29. chrono and sleep_for 30. std::invoke 31. async, future and promise 32. std::launch::deferred 33. std::launch::async 34. silly operator overload (don't do this) 35. user-defined literals 36. std::chrono_literals 37. digits separator 38. std::string 39. std::string_view and range-for 40. string literals 41. constexpr 42. just "Hello"