Posts

Some Thoughts on Interfaces

  One principle that I have seen enunciated is that one should not have an interface with only one implementation. This is generally sensible. Why separate out an interface if there is no variation? You can get physical insulation without an interface, if you need one, using the PIMPL idiom. (This approach explicitly rejects the idea of creating an interface/implementation pair just because you expect a need to add another implementation in three months. Just be aware that coding in that context needs to be careful - the first time you create a class which owns an instance of the class with potential variation, and it doesn't do so through a pointer of some sort, you're adding more work than is necessary down the line when you suddenly need to be holding a pointer to an interface. My own view is that if you really know that a second implementation is barrelling down the tracks towards you, there's no reason not to put the eventually-required separation of responsibilities ...

Structured Bindings

Structured bindings tend to be dismissed in many discussions online as very minimal improvements to C++. There isn't very much you can do with them that's not on the package, so to speak: returning multiple values, iterating easily over maps, and initializing more than one variable in an if or switch scope. The point, however, is not what they allow you to do: everything you can do with them you can do without them. The point is how it is done. If we compare: auto [var, flag] = func(); with, say auto val = func(); auto var = val.first; auto flag = val.second; it's not just that we take one line instead of three to get the same result, it's that the latter code has to pay more attention to how rather than what. It draws attention to the fact that was was returned was a pair. There are at least three other possible mechanisms behind that first line. auto val = func(); auto var = val[0]; auto flag = val[1]; (an array) auto val = func(); auto var = get<0>(val); auto f...

Decent First Drafts

Let's say that you are implementing a Composite pattern for an interface with several interface functions. For functions which return void, it's straightforward to anticipate that the implementation will be a simple for_each. For functions which return a value, something has to be done to convert multiple return values into one. There are several obvious patterns: - For functions like size() you call accumulate/fold_left and return an accumulated value.  This can't be extended arbitrarily to functions returning a numeric type: you might want, for example, to return a maximum value. - For functions which return a boolean status, you return the results of applying std::none_of to a test for a false return, std::all_of to a test for a true return. - For functions generating a string representation you return a string concatenating outputs with interpolated delimiters and possibly a beginning and ending. The same applies as a special case of output functions which return void b...

From Template Method to Delegation

Here is a mildly interesting refactoring from a use of the GoF Template Method to a use of delegation injected into a parent class. In determining the Lauds and Vespers hymns on a seasonal basis, most are straightforward and do not vary by Use: Roman and Sarum uses agree, and the hymns are the same throughout the season.  But for days during Ordinary Time these vary by day of week and in one of the two periods of Ordinary time the Roman and Sarum uses do not agree: Roman use uses the same pattern as during the other period, but Sarum use uses one hymn per office for the whole  season (which is the same as the Roman Sunday hymn). The original solution to this was to use a form of the GoF Template Method pattern. A switch statement which generally returns fixed values has four functions it calls for the relevant cases:   switch (inOffice)     {       using enum OfficeNames;     case COMPLINE:       return ComplineChapter::Ge...

Clarifying functionality

Many conversions of loops to STL forms are near to net-zero in terms of complexity changes: From for (int i = 0: i != r.size(); ++i)     vec.emplace_back(r[i]); to for (auto iter = r.begin(): iter != r.end(); ++iter)     vec.emplace_back(*iter); to std::for_each (r.begin(), r.end(), [&vec](const auto& inVal) {     vec.emplace_back(inVal); }); to std::copy(r.begin(), r.end(), std::back_inserter(vec)); to std::ranges::copy(r, std::back_inserter(vec)); gives us five versions of the same operation in the same ballpark as far as figuring out what is going on, with only the last really giving us a noticeably shorter form because of the simpler form of the range algorithms.  (The last version is also almost certainly more efficient than the first version.) The biggest difference is that one can basically read the last one from left to right as "copy the contents of r onto the back of a vector vec". Some changes, though, can do a great deal to clarify ...

Revisiting the FileLineSource utility

A number of posts ago, I talked about a small utility called FileLineSource, which did a little bit of complexity hiding when reading lines from a text file. That utility had one implicit issue: it perpetuated the interface of std::getline(), for which it was, essentially, a drop-in replacement.  That meant that it was entirely functional, but it did not support the use of STL algorithms. Thus its use was something like this:   auto source = inFileFactory.create();   source->openSource();   std::string s;   while (source->getNextLine(s))     m_records.emplace_back(s); This could be improved upon.  It uses a C-style while loop, it's spread out over five lines, and it needs a bit of attention to see what it's doing. The way to address this was not to change the utility itself, but to add a wrapper.  Because the use for this corresponds to an input range, the simplest of ranges, we can provide a very simple wrapper which provides the missi...

Eliminating while

(This can be considered to be a meditation on one aspect of Sean Parent's "no raw loops" dictum.) The while loop is about as close to the bare metal as you can get while using structured programming idioms. It translates effortlessly into a test and a goto and in many cases the test is a couple of assembler instructions if the while loop is testing a location in memory (e.g. while(*cp++ == ' ')). If you need speed in a tight inner loop in a highly time-sensitive application, while is an excellent candidate. There are two other idiomatic uses of while() which are, as one might say, "worthwhile". The first is while(true) { ... } as an idiomatic way of marking an infinite loop which will be terminated only by a resource failure or program shutdown. (But see below for why this is not ideal when used to delimit a loop which can be terminated by an internal break statement - and this includes control loops for threads which have to terminate cleanly at exit.) ...