Stunt programming. Modern C++ is not about that.

Update: I have added page 4 “User Manual”

Update: I have expanded “Option 2” with a working solution. And hopefully explained why is it very likely, still a stunt.

Stunt programming? Here is one definition. From C2 wiki

Stunt Programming

Doing things the hard or risky way because it is more exciting (and exciting is FUN…until you crash).

I could not agree more with this definition. I have also given a practical example using C#, on the same subject sometimes ago.

Modern C++ provides a LOT of opportunities to engage and get one self-lost in this kind of programming.

(This is one fairly long post with a lot of C++ code inside.  For newcomers to modern C++, but not for beginners. )

Stunt Programming
No this is not me. This is one Stunt Programmer in action.

As we deliver the code we will discuss the findings.

Requirement: provide application-wide storage for a default value of any type. Deliver the functionality to use it, share it and change it by users.

Note: We will provide the bare minimum.  No resilience in presence of multiple threads, no move semantics and no other modern C++ “stunts”.

To make the code a bit more realistic we shall use few enums from the Gdiplus:: namespace.  GDI+ is a well-known artefact to WIN32 aficionados.  To use it one has to place these lines in the header:

And now forward to the …

Solution design

After a shorter think about the requirements we decide we shall provide the following interface to the solution:

The above will return the same predefined default value on repeated calls. And will work over compilation units; it will be global.

To implement this we will use the static value, nested inside a function. Default argument C++ idiom defines the default width. In some later iteration, this might be coming from an external configuration.

Thus we will achieve lazy instantiation and persistence during the program lifetime. Now the task is simple. Implement the above for any type and any name.

Option One

Hot on the plenty of advice by modern advisers on modern C++, our imaginary programmer friend,  tries to “think” as them and quickly produces the skeleton of the template-based function.  After all, classes (so it seems) are not “en vogue” these days. And he seems to remember seeing some quite exciting mixture of modern C++ features on the subject of templates and functions. So here is the first stab at the first option.

Testing

Indeed this looks rather promising. And it compiles too. But there are problems with other code. Here is the one-line problem:

auto default_problem = static_default < REAL, REAL{10} > ;

error C3535: cannot deduce type for 'auto' from 'overloaded-function' error C2440: 'initializing': cannot convert from 'T &(__cdecl *)(const T *)' to 'int' note: Context does not allow for disambiguation of overloaded function above template instance is not "different enough" from two previous ones 

GDI+ Legacy C++ attacks. We try and solve this but things suddenly do not go as smooth as they seem to go when advisers present the same kind of solutions. Alas, not using real-life legacy libraries.

Especially the usage seems troublesome.  We want some clever snippets here. But things do not seem so snazzy all of a sudden.

It seems our modern C++ compiler is “not obeying” when instancing this template :
auto the_problem = static_default < REAL, REAL{10} > ;

Why is this? Why does it produce “int” instead of function pointer on this line and not on the instances elsewhere? Even if we decode that we can not change the GDI+ code, so we will not go there.

Hm. OK, legacy is always a big issue. We know better, and we “take the step back” and take a different path.

[nextpage title=”Option Two”]
Lambdas!

 

Yes. That divides modern C++ man,  from old C++ boys! We remember the factory pattern and we will use it here too.  Thus showing the unmistakable OO pedigree, too.

We will deliver the function template returning a lambda! Close to real stunt, but works And compiler has fewer problems disambiguating the instances

Looks much more in line with our “c++ ninja” status. Does it not? Very clever little stunt that is!

Testing

Also. The real modern C++ thinking shines through. Functional programming and the rest.  Function pointers are nicely (and obediently by the compiler) made. auto is used properly so there are no ugly pointers to instances of template functions. Or was it function templates?

But. Few little problems here too. First this pesky default argument issue. It seems the good old argument as “pointer to null” works the best as the default argument idiom. It seems it is far from modern C++. We try the “modern” solution on that line:
return [&](const T & new_ = defval_) { ... } 

But alas no can do:

error C2587: 'defval_': illegal use of local variable as default parameter

Oh? That is a bit of a surprise.  We quickly “Google out” for some advice but it seems nothing works. Time passes by.  Fine. Is this really a problem?

Well yes, it is. If we pass the pointer we need to guarantee it points into the meaningful value. This line is especially troublesome:

default_value = *new_;

We make two big assumptions here: first that de-referencing will produce a meaningful value of type T, and second that operator “=” exists and can take the right-hand side as we wish it to.

Hmm. Not entirely sure here. Should we try hard and pull some latest modern C++ tricks here? C++17 guides? Or some yet undiscovered nugget from the std lib.

And yes we also have noticed this solution does not work as we expect it to. It simply does not return the default value on the first call? Hm. Hm.

Working option Two

I am not an omnipotent C++ guru but let me jump in now and help our bright but somewhat unfortunate programmer and help provide a C++17 solution. With errors in logic removed too.

As before we will stay with the idea of function template returning a lambda. So what do we do to make this behave as we want it to?

On the first call, we execute the lambda and return it. Thus on the second and all the other calls, it will be used with a proper def value from the first call. You know: if we try simply  T new_ = defval_  that will not compile.

The elegant  (C++17) way around, is to use std::optional

(NOTE: lambda bellow requires T to be a movable type so if T is your class it better be movable)

Yes, this is a real modern C++17. std::optional<> and a such. And it works too.  So what could be possibly wrong with it?

The reality bites. It is very likely:

  • Your team/company is still on C++11, or is it C++03?
    • std::optional requires (at least) C++17
  • To your team, this code is pretty hard to understand
    • yes this is clever but definitely on the edge of “stunt”
  • To maintain or expand this code  authors presence is required
  • Here insert your favourite show stopper  …

What should we do? We should stop here and think. How would we actually implement this if we focus on the requirements instead of the latest features possible with the programming language we use. After all, if something is in there, in modern C++, that does not mean we have to use it.

Imperative (in case you forgot) is to solve the requirements. In a feasible manner.

[nextpage title=”Option Three”]

Sobering time.

Cold head.  May I remind you we all know very well about the good old Function Object aka “Functor”?

So let’s do a proverbial step back and use what we already know. Bellow is a very bland looking very traditional C++ solution.  I am not sure but perhaps even the C++98 compiler could produce a decent byte code from this?

Testing begins here

I think this is one sober solution indeed. And working too. Usage seems simple and with no surprises.

Also, the default argument issue is solved in the C++ way. Modern or not. By using () operator overload, on the class.

What also is delivered is correct behaviour in case of const required.  Code above is documented. And easy to maintain, expand or do whatever might be required down the line.

[nextpage title=”User Manual”]

Due to popular demand

Here is a short user manual on how to use "dbj  defval" Option 2.2 and Option 3.

First please recall the top of this article. We are using GDI+ to add some dose of reality to these usage examples. And the dose of legacy code issues.

How to use Option 2.2

Declared by me as “probably a stunt” but of course, perfectly usable.

You declare and define inside some namespace the global instances; after which callers from possible separate threads can use. Or change. I am sure you know how to make that “resilient in the presence of threads”.

How to use Option 3

note: c++ anonymous namespaces make variables inside them have internal linkage. I very rarely use them. Please do use named name space’s around these examples.

You declare and define  dbj::holder<> instances as (c++17) inline globals. Then every caller from some thread uses them.  Please keep in mind calls above could be scattered across a few threads, but that would make the code longer and will obfuscate the usage pattern.

I hope you find this post useful and not difficult to grasp. Of course, there are many other solutions but the message is one:

Simplicity rules

 

So little C++ so much good!
So little C++ so much good!