Last November 15th I attended the Italian Agile Day in Ancona, where Gianluca Padovani, Marco Foco, and I facilitated a workshop on refactoring in C++ (if you like, read more details here). Before our session, I attended a couple of talks and one was about “IF-Oriented Programming vs Object Oriented Programming”, by Claudio Pattarello (a former collegue of mine). I enjoyed this talk because it was completely on the code (in C#) and rich of interesting examples on removing IFs. I have attended this kind of talks for at least 8 years (the first time was at the university, in 2006) but this time something was different: each solution Claudio introduced could be written in C++ with almost no effort. This wouldn’t have been possible without new C++.
In this post I’d like to show you how to rewrite these idioms in C++, starting from a C# snippet. And the resulting code is pretty much identical. Here are Claudio’s examples. First let me clarify:
- I’m skipping visitors because they are well-known in C++ (and from C++11, variadic templates could help even more);
- Claudio’s target was to show different ways to remove IFs and to play with the code. The real point is: don’t tell “the IF cannot be removed”, but instead, wonder if it’s worth. For this reason, I’m not discussing edge cases nor saying these rules apply everywhere (they don’t)..
- I’m not saying “this is the best thing you can do in C++”. For example: as Nicola suggested, the first example I’m showing could be rewritten by using an optional type, that is (likely) a more effective thing to do in C++ and it results in less code. Sure, we have specific idioms in our favorite language but I think it’s lovely that we are able to reuse the same code from other languages with a little effort. And this is also appealing for other programmers who want to experience C++, maybe by starting coding idioms they know. Please, stop me if I’m raving!
Let’s start with an example you have to deal with a null. Here is the first version of the code:
public void NullResult_IF() { var employeeRepository = new EmployeeRepositoryNullIF(); var employee = employeeRepository.GetOrNull("CLAPAT01"); if (employee!=null) Assert.AreEqual("Claudio", employee.Name); }
What if the repository does not contain the name we are looking for? It returns a null. Null case has to be handled. Suppose you have to fill a form with employee’s data. Using a NullObject doesn’t help so much because you won’t fill a form with fake values.
Then, the proposed solution is to add a layer of abstraction and pass an action instead of checking the null case by hand:
public interface IPerformer<out T> { void Ask(Action<T> action); } public class Performer<T> : IPerformer<T> { private readonly T value; public Performer(T value) { this.value = value; } public void Ask(Action<T> action) { action(value); } } public class NoPerformer<T> : IPerformer<T> { public void Ask(Action<T> action) { } } public class EmployeeRepositoryNullNOIF { //... public IPerformer<Employee> Get(string number) { var employee = repository.SingleOrDefault(e => e.Number == number); if (employee==null) return new NoPerformer<Employee>(); return new Performer<Employee>(employee); } } public void CentralisedNullResult_NOIF() { var employeeRepository = new EmployeeRepositoryNullNOIF(); var employee = employeeRepository.Get("CLAPAT01"); employee.Ask(e => Assert.AreEqual("Claudio", e.Name)); }
employeeRepository now returns an IPerformer<T>, an interface you can ask to call a lambda which wants a parameter of type T. In this example, the lambda will be called only if the T “is valid”. An alternative consists in providing also a lambda for the failing case. But now the juicy part: what does it look like in C++? This is a possible migration:
#include <functional> #include <iostream> #include <memory> #include <map> using namespace std; template<typename T> class IPerformer { public: virtual ~IPerformer() = default; virtual void Ask(function<void(const T&)> f) = 0; }; template<typename T> class NoPerformer : public IPerformer<T> { public: void Ask(function<void(const T&)>) override { } }; template<typename T> class Performer : public IPerformer<T> { public: Performer(T _value) : value(move(_value)) { } void Ask(function<void(const T&)> action) override { action(value); } private: T value; }; struct Employee { string Name; string Last; string Number; }; class EmployeeRepositoryNullNOIF { public: unique_ptr<IPerformer<Employee>> Get(const string& number) { const auto it = employees.find(number); if (it != end(employees)) return make_unique<Performer<Employee>>(it->second); return make_unique<NoPerformer<Employee>>(); } map<string, Employee> employees = { {"1", {"Marco", "Arena", "1"}}, {"2", {"Claudio", "Pattarello", "2"}} }; }; int main() { EmployeeRepositoryNullNOIF repo; auto employee = repo.Get("2"); employee->Ask([](const Employee& e) { cout << e.Name << " " << e.Last << endl; }); }
It’s just a toy, but you may play with this example. Ha, I forgot to say: C++ examples here are editable and runnable (thanks to Coliru online compiler and ACE editor). Then, try the code yourself!
C++’s control is finer, thus I considered passing T to Performer by value and then move-construct the internal one; also, T is passed to the function by const&.
Apart from these details, C++ code is pretty much the same as the C# one.
The next example is a variation of the previous one. It’s about doing something with the result of an operation if it succeeds, otherwise doing something else. Trivial. Here is the C# snippet:
public void ConvertNumber_IF() { int number; if (int.TryParse("38", out number)) Assert.AreEqual(38, number); else Assert.Fail(); }
To remove this IF, Claudio used a lambda, again:
public void ConvertNumberWrapIf_NOIF() { Action action = Assert.Fail; ConvertNumberStatusNOIF.Parse("38", number => { action = () => Assert.AreEqual(38, number); }); action(); }
As before, the lambda gets called only if the operation succeeds. C++ code is embarrassingly similar:
#include <iostream> #include <sstream> #include <functional> using namespace std; class ConvertNumberStatusNOIF { public: static void Parse(const string& number, function<void(int)> action) { stringstream ss(number); char c; int age; if (ss >> age && !ss.get(c)) action(age); } }; int main() { function<void()> action = []{ cout << "fail\n"; }; ConvertNumberStatusNOIF::Parse("38", [&](int number) { action = [=]() { cout << number << endl; }; }); action(); }
A variation consists in making action a sort of scoped function (i.e. that will be executed when the scope ends – I saw this kind of workflow to handle DB transactions).
Going ahead, the following snippet handles error conditions by catching exceptions explicitly:
public void CatchExceptions_IF() { var promocode = new PromocodeStatusIF(); try { promocode.Apply("g128g7d2g"); } catch (AlreadyUsedPromocodeException) { Assert.Pass("Already used"); } catch (ExpiredPromocodeException) { Assert.Pass("Expired"); } catch (NotValidPromocodeException) { Assert.Pass("Not valid"); } Assert.Fail("no exception"); }
Try/Catch can be seen as another kind of IF, maybe more structured. To remove yet another IF, we pass actions instead and we let the promocode checker call the right one for us:
public void RemoveCatchExceptionsAndUseMessages_NOIF() { var promocode = new PromocodeStatusNOIF(); promocode .AlreadyUsed(() => Assert.Pass("Already used")) .Expired(() => Assert.Pass("Expired")) .NotValid(() => Assert.Pass("Not valid")) .Apply("g128g7d2g"); Assert.Fail("no exception"); }
PromocodeStatusNOIF just calls the right action. No exceptions are involved, no manual handling to do, adding/removing logic is trivial. What does it look like in C++? Not so different:
#include <iostream> #include <functional> #include <string> using namespace std; class PromocodeStatusNOIF { function<void()> alreadyUsed = []{}; function<void()> expired = []{}; function<void()> notValid = []{}; public: PromocodeStatusNOIF& AlreadyUsed(function<void()> action) { alreadyUsed = action; return *this; } PromocodeStatusNOIF& Expired(function<void()> action) { expired = action; return *this; } PromocodeStatusNOIF& NotValid(function<void()> action) { notValid = action; return *this; } void Apply(const string& promocode) { // logic... expired(); } }; int main() { PromocodeStatusNOIF{} .AlreadyUsed([] { cout << "Already used\n"; }) .Expired([] { cout << "Expired\n"; }) .NotValid([] { cout << "NotValid\n"; }) .Apply("g128g7d2g"); }
I don’t have the final Assert.Fail because we are not really testing anything. A variation is to add the “no exception” action either in the constructor or as another function of the chain.
The last example I show you is a singleton factory which the first time you ask an instace it creates and returns a fresh one, other times this instance is reused. The IF solution is straightforward:
public class FooSingletonLazyFactoryIF { private Foo foo; public Foo Get() { if (foo==null) foo = new Foo(); return foo; } }
Removing the IF is a good exercise of self-modifying lambdas:
public class FooSingletonLazyFactoryNOIF { public FooSingletonLazyFactoryNOIF() { Get = () => { var foo = new Foo(); Get = () => foo; return Get(); }; } public Func<Foo> Get { get; private set; } }
Forget using a local static variable, just enjoy a different way to code. What does it look like in C++? This is a possible implementation:
#include <functional> #include <iostream> using namespace std; struct Foo { Foo(int _magic) : magic(_magic) { cout << "new Foo" << endl; } int magic = 0; }; class FooSingletonLazyFactoryNOIF { public: FooSingletonLazyFactoryNOIF() { _Get = [this] { _Get = [foo=Foo{256}]() -> const Foo& { return foo; }; return _Get(); }; } const Foo& Get() const { return _Get(); } private: function<const Foo&()> _Get; }; int main() { FooSingletonLazyFactoryNOIF factory; auto&& foo = factory.Get(); auto&& foo2 = factory.Get(); cout<< foo.magic << endl; cout<< foo2.magic << endl; }
Thanks to init-lambda capture I was able to initialize Foo directly in the lambda. This version has a problem: what if the factory goes out of scope? The internal foo is destroyed and we have a dangling reference. Ok, you can make the factory a global object but there is another solution using smart pointers. Don’t try to move-capture a unique_ptr into the lambda because it is not possible to construct a std::function from a move-only type (cfr. §20.9.11.2.1 [func.wrap.func.con]). We can use a shared_ptr and return it, or return just a Foo*, or a weak_ptr. For example:
#include <functional> #include <memory> #include <iostream> using namespace std; struct Foo { Foo(int _magic) : magic(_magic) { cout << "new Foo" << endl; } int magic = 0; }; class FooSingletonLazyFactoryNOIF { public: FooSingletonLazyFactoryNOIF() { _Get = [this] { auto foo = make_shared<Foo>(256); _Get = [foo=move(foo)]() { return foo; }; return _Get(); }; } auto Get() const { return _Get(); } private: function<weak_ptr<Foo>()> _Get; }; int main() { FooSingletonLazyFactoryNOIF factory; auto foo = factory.Get(); if (auto spt = foo.lock()) // not needed actually { cout << spt->magic << endl; } }
As you can note, C++ snippets are very similar to C# ones. Since C++ gives more control and it doesn’t have a garbage collector, you need to be careful of more aspects. In particular, dealing with self-modifying lambdas could lead to several problems like capturing local variables by reference.
Using std::function is not optimal. Not only because of its runtime cost but also because of its inability to work with move-only captured types. I’d like also to share a fact you could care about when dealing with self-modifying lambdas. Suppose you wrote this circular object pool by using the same ideas we discussed a moment ago:
class Pool { function<int()> generator; function<int()> next(vector<int> v, size_t i) { return [this, v=move(v), i]{ auto tmp = v[i]; auto size = v.size(); generator = next(move(v), (i+1)%size); return tmp; }; } public: Pool(vector<int> v) : generator {next(move(v), 0)} { } int operator()() { return generator(); } }; //...user code Pool pool { {1,2,3} }; cout << pool.Get() << endl; // 1 cout << pool.Get() << endl; // 2 cout << pool.Get() << endl; // 3 cout << pool.Get() << endl; // 1
You are storing the vector inside generator (which is a std::function). How many copies of this vector do you expect? You could say: “Zero! Just moves, because I’m always moving the vector from a lambda to the next one”. I thought so too. Look more carefully how you are updating the next function: when you capture the vector by move it gets moved into the lambda. Ok. What next? You store tmp and size, ok. Then? You update generator… Ah! Moving the vector here is just a copy! Why? Because the lambda is not mutable, thus the vector is const and moving a const object is actually (and silently) a copy. I prepared this snippet to let you play (i.e. remove mutable):
#include <iostream> #include <memory> #include <vector> #include <functional> using namespace std; struct sentinel { sentinel() = default; sentinel(const sentinel&) { cout << "copy" << endl; } sentinel(sentinel&&) { cout << "move" << endl; } }; class Pool { function<int()> generator; function<int()> next(vector<int> v, sentinel s, size_t i) { return [this, v=move(v), i, s=move(s)]()mutable{ auto tmp = v[i]; auto size = v.size(); generator = next(move(v), move(s), (i+1)%size); return tmp; }; } public: Pool(vector<int> v) : generator {next(move(v), sentinel{}, 0)} { } int operator()() { return generator(); } }; int main() { Pool p { {1,2,3} }; cout << p() << endl; cout << p() << endl; cout << p() << endl; }
Target of this post was purely to implement Claudio’s examples in C++, taking into account some particularities of our favourite language. As I said, I’m not discussing edge cases nor saying these idioms apply everywhere (they don’t). If you have more examples please share them here!
Great post!
I know Bartosz Milewski is just going to say that the first examples are istances of the Maybe monad! Anyway, the fact that the code directly translates from C# doesn’t mean it is necessarily a good thing to do. In this case for example, object-oriented runtime polymorphism is useless. I think it can be done directly with optional instead of rolling your own IPerformer interface etc…
Regarding the singleton factory: just use a static variable inside the factory function and return it by reference. Statics are thread-safe in C++11, while your example is not, and you don’t have any “if” because is what statics are meant for.
To summarize: if there’s a more “C++-ish” way of doing things, when porting code, I prefer to exploit it 🙂
As I wrote at the beginning (and as Claudio said during his talk), these are just examples of doing the same thing in another way. Sure, we have specific idioms in our favourite language but I think it’s cool that we are able to reuse code from other languages with almost no effort! This means C++ is open to everyone!
About optional, it requires an IF ( e.g. if (opt.is_valid()) ) 🙂 About the static variable I expressly told not to use a static variable! Because it’s too obvious! Let’s play with the code 🙂
PS the real reason for not using the static here is that you want a new Foo instance for each factory object:
Factory factory;
factory.Get(); // new instance
factory.Get(); // reuse
// …
Factory factory2;
factory2.Get(); // new instance
factory2.Get(); // reuse
Yes, let’s play with the code 🙂
Anyway, just to elaborate on my words about optional: your code too contains an if, but it’s hidden. So if we’re hiding the if away in library code, rather than eliminate it, then you can do it with optional, too, and it’s even better because it’s chainable (and it’s chainable because it’s monadic)
using namespace std::experimental;
template const&opt, F&& f) {(f)(*opt);
auto operator>>=(optional
if(opt)
return std::forward
else
return null_opt;
}
and you use it like.
myopt >>= [](T1 *arg) { return ....; } >>= [](T2 *arg) { return ...; }
The next version of the Foundamentals TS will for sure add a member function to optional to do exactly that, so it will be still easier.
Anyway, the point of your post is true: you can adapt C# code to C++ quite easily with modern features 🙂
Happy you’re seeing monads in C++ 🙂 (Bartosz would be proud of you!).