2

On stackoverflow I asked, what is the preferred way to expose custom STL-style iteration?. The answer seems to be to implement twelve functions: six members, six non-members (perhaps using some macros to assist).

In C#, I can do about the same with just a single function using yield return; C++ is great, but it's things like this that can give it a really bad reputation. Is there a good (i.e., macro-free?) way to not have to write these twelve functions?

  • 1
    Did you read through the answers and comments on the linked question? The answer you are looking for is already there. – user22815 Aug 31 '16 at 13:59
  • @Snowman yeah...use macros. :-( – Ðаn Aug 31 '16 at 14:01
  • 1
    @Snowman I'm trying to nicely say "WTF!" – Ðаn Aug 31 '16 at 14:02
  • 3
    IME the best way to not [insert unnecessarily painful thing here] in C++ is to not use C++. It's one of those "now you have two problems" things. – Mason Wheeler Aug 31 '16 at 14:06
  • 1
    How many of these classes are you building, and who is maintaining them? There's some CRTP trickery you can do with templates. It's typesafe, unlike macros, but I don't think it's any more readable. – Cort Ammon Aug 31 '16 at 14:30
3

The global functions std::begin and std::end (along with their c counterparts in C++14) will find the named member functions of the containers. So there is no need to provide non-member versions of these functions.

Non-member ranges only matter if someone wants to make an unqualified call to begin/end. And the Container concept in C++ does not present that as a valid interface to containers (not that it should matter to you, since few C++ APIs take containers to begin with). Range-based for will look for member begin/end before non-members, so you're safe there too.

But the member function you are going to have to write yourself. You can avoid writing the cbegin/cend versions by writing const versions of begin/end and using the CRTP to generate the others:

template<typename Derived>
class cbegin_cend
{
public:
    auto cbegin() const {return This()->begin();}
    auto cend() const {return This()->end();}
private:
    auto This() const {return static_cast<const Derived*>(this);}
};

To use it, you would do this:

class mine : public cbegin_cend<mine>
{
public:
    const_iterator begin() const;
    const_iterator end() const;
};

This requires C++14's automatic return type deduction functionality. That's required because otherwise you'd have to type the name of the return type in cbegin and cend. And the CRTP base class cannot detect that without external help. We could use an external traits class to provide it, but that's extra work the container has to do.

  • Yet this answer specifically says the non-member functions are necessary. – Ðаn Aug 31 '16 at 16:36
  • 1
    @Dan: No, he said that they are necessary in order to allow unqualified calls to begin/etc to work. If you don't intend to make or support such things, then you don't need them. Certainly, the container requirements don't require it (not that C++ has many functions that use containers as a concept to begin with). And even range-based for will prefer member begin/end over unqualified begin/end – Nicol Bolas Aug 31 '16 at 16:41
  • But if I don't know how clients are going to use the code, then for maximum flexibility I should support the non-member begin()/etc. calls, right? If all the code is completely under my control, then that might not be necessary. – Ðаn Aug 31 '16 at 17:03
  • 1
    @Dan: "But if I don't know how clients are going to use the code, then for maximum flexibility I should support the non-member begin()/etc. calls, right?" Why should you? Generally speaking, if you don't provide an interface, people won't try to use it. If you say that your interface follows the Container requirements from the standard, then people will expect it to provide that interface. But the Container requirements don't include unqualified begin/end calls. So why would your users expect you to provide that? – Nicol Bolas Aug 31 '16 at 17:14
  • I dunno ... hence the original question on SO about how to best write an STL-style iterator. If the answer is that you "only" need six (or fewer?) member functions, that would make the task less onerous. – Ðаn Aug 31 '16 at 17:17
5

On stackoverflow I asked, what is the preferred way to expose custom STL-style iteration?

... In C#, I can do about the same with just a single function using yield return

No, you really can't.

C#'s yield return looks similar to a Python generator, which in C++ terms is an InputIterator, the simplest of the six iterator categories described here.

Your original question implements a RandomAccessIterator, which is significantly more powerful than this - for example, you can do direct random-access binary search on the iterators returned in that code, or sort the contents of a non-const container in-place.

If all you want is a C#/Python style input iterator, you can probably simplify production with CRTP as Cort Ammon suggested in comments, and you can definitely lose the non-const overloads.


A summary of subsequent comments:

Q: In PogoStickLang, I can create a new pogo stick by just typing !. In RocketLang I need to type loads more stuff to make a pogo stick, and it seems over-complex

A: In RocketLang you created a rocket, which is why it's more complex than a pogo stick. Either

  • create a pogo stick if that's all you want. It's more work when not using a language tightly focused on the unique needs of pogo stick-users, but still less work than creating a rocket
  • or, create a rocket and actually take advantage of all the things it can do that a pogo stick can't, like flying to the moon or incinerating your enemies.

No, this isn't entirely serious. No, I'm not suggesting that C# is a toy language, and no, I'm not suggesting this is a proportionate comparison of the relative powers of Input- and RandomAccessIterators. I don't have anything against pogo sticks and rarely, if ever, incinerate my enemies.

  • That is a good point - if the container is immutable, only provide const iterators. If it is mutable, only provide non-const iterators since they can be used in a const context anyway. – user22815 Aug 31 '16 at 15:16
  • No, yield return is not the same; but it's "close enough" (at least as far as a lot of developers are concerned). – Ðаn Aug 31 '16 at 15:24
  • 3
    A lot of developers are looking for the features they expect to see, and ignoring the ones they didn't know existed. If you limit yourself to implementing what they expect, it'll be less typing, although still more than with language support. On the plus side, the more powerful iterator categories do actually exist in C++, where C# has language support for the most basic category and no support at all for the others. – Useless Aug 31 '16 at 15:34
1

No.

As the linked question states, the Container trait in C++ indicates you need twelve functions for iteration in various circumstances: const v. non-const, member v. non-member, etc.

You may be able to use a macro to avoid some of the boilerplate, but now you have less visibility into your code. What if there is a bug in the generated code? Can you look at your C++ source file and see it? No: the extra step of inspecting preprocessor output is required. There is a reason why macros are generally avoided in good C++ code outside of include guards (yes #pragma once exists but include guards are still common). They makes code less readable and harder to maintain. This is part of the reason why C++ gives us tools such as templates and traits, to address the root cause of some of the reasons for using macros.

Functions to acquire iterators are generally short and simple. Just hand-write the functions and be done with it.

  • 1
    How often do you really write (real) custom containers ? – Nikko Aug 31 '16 at 14:27
  • 3
    @Dan If you want simple to write custom containers do not use C++. Simple as that. – Andy Aug 31 '16 at 14:28
  • 2
    Any language has complexity. Modern languages like C# hide a lot of it in library code. In C++, you often have to hit that complexity head-on and deal with stuff like writing custom iterators. But that lets the code that uses those iterators be simpler. – user22815 Aug 31 '16 at 14:30
  • 1
    Car analogy: C++ is like a 1970s muscle car. You buy it, then complain about having to tune the carburetor. After all, your other brand new 2016 car is all digital and tunes its fuel injection automatically around ten times per second. Both cars have plenty of horsepower and do well in the quarter mile, but the classic one looks nicer and makes you more attractive to the opposite gender. It just takes more work. – user22815 Aug 31 '16 at 14:34
  • 2
    Maybe "yield return;" in C# allows some programming style that would be done completely differently by a C++ developper (maybe function object, lambda, exposing containers) depending on what you need. – Nikko Aug 31 '16 at 14:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.