Posts

Small and Large Lambdas

Sometimes lambdas seem like a gift from heaven.  They allow the quick generation of STL algorithms without all the headache of setting up the structure of a unary or binary op class, and the ability to create general closures with a single extra character allows not having to worry about specific capturing of members by reference or pointer for use inside the function.  In C++14 and following the ability to define a stand-alone lambda with a given name allows for extra clarity when a given operation has to be executed several times but with a decidedly local context. (If I'm repeating the test-and-set pattern of  auto setImplementations = [&](std::string_view inVal) {     if (m_implementations.get() != nullptr)       throw std::runtime_error(           "Only one of -C, -d, -D, or -i allowed as type specification");     m_implementations = m_factory.create(inVal, m_names, m_dataRequirements);   }; four ti...

Injection versus Encapsulation

We (justly) hear a lot about dependency injection, and in many cases straightforward dependency injection, whether managed via a tool like Swing or just manually (as you have to do in C++) is clearly the best thing to do.  In most of these clear cases the dependency is public and shared: one common call will not only be economical in terms of code, but will allow the class into which the dependency is being injected to become less tightly coupled to its environment. But there are some interesting edge cases.  Excluding those cases where a strategy is purely private to a class -- that is, it is a resource exactly like one which might be injected, but it is properly an implementation detail  of that class and, if visible outside the class, is so purely to allow it to be tested independently[1] -- we have cases where although the resource is not unique to one class its implementation will be different in some way locally.

Generating Actions: A Small Design Discussion

Here's a mildly interesting small-scale design choice. I'm generating a set of actions corresponding to a set of states which are represented by monotonically-increasing enums. (And when I say generating, I mean it: this is actually code produced by a code generation tool I'm writing, not manually.) Each concrete instance implements the interface: class ITransmogrifierAction { public: virtual ~ITransmogrifierAction(); virtual TestEnum type() const = 0; virtual std::pair<Token,TestEnum> process(const TokenType& inVal) = 0; }; and the array being filled - std::array<std::unique_ptr<ITransmogrifierAction>, 5> m_actions; - will end up being a lookup for state machine transitions. The obvious STL way to do this is via generate_n. class Filler { public: std::unique_ptr<ITransmogrifierAction> operator()() { switch (m_n++) { case 0: return std::make_unique<AlphaTransmogrifierAction>(); case 1: retur...

State Machines

Almost any program can be modelled as a state machine. I say "almost any" because it is, just, possible to think of a program which cannot easily be resolved into anything more than a one-state state machine. Consider a command interpreter with no support for loops or function definitions (like the old DOS command.com) and with no internal commands, only the ability to execute external commands. It loops forever and does only one thing for every line it reads.    If it does no error handling, the execution is a single line, a call to execve or one of its relatives. There are internal stages inside the C library call, but they are effectively invisible to the program.  However, it's not a very useful program. Neither is any version of HelloWorld. The echo application does have its uses. Once one gets beyond this, even simple programs have at least two states: setup and execute. A simple version of cp has to open two files for input and output before beginning to copy data....

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...