memdump

Declarative Control-Flow in C++

So what exactly is this thing? It’s a “programming idiom” leveraging some advanced compiler features to allow error-less and most notably well-structured definition of control flow within functions in modern C++. By definition, it closely resembles declarative style of programming, thus the name “Declarative control-flow”. This is not yet a feature of the C++ programming language, but there is an effort(isocpp) to make it part of the standard. For the time being the use of third party libs is required in order to use this function (Note: when using Visual Studio 2015, it should work out of the box). If Boost is already part of your build then go for ScopeGuard. For every other case, I can highly recommend Evgeny Panasyuk implementation (all the examples will use this library).

Usage

Before getting into all the details and the background, lets see how it works in real life. All you have to do is to create a stack_unwinding::scope_action variable using the stack_unwinding::make function which takes as an argument an Action (essentially a lambda function).
There are 3 supported action types:
stack_unwinding::scope_failure
stack_unwinding::scope_exit
stack_unwinding::scope_success
So for example in order to print a message into stdout before a function returns, place the following piece of code anywhere within your function:

//print a message when returning
stack_unwinding::scope_action at_exit
	= stack_unwinding::make<stack_unwinding::scope_exit>(
		[=]()->void {std::cout << "Leaving function Foo()" << std::endl;}
	); 

Now let’s assume that you have a function which does some kind of simulation and you want to store the results of the simulation, but only if the simulation was successful. You could either use the classic try catch if else combination (and hope that the flow of the simulation doesn’t change) to call the function responsible for storing the result of the simulation, or you can go for the declarative way of doing things:

//last step of simulation
auto result = calculate_final_step();

//store simulation results 
stack_unwinding::scope_action at_success
	= stack_unwinding::make<stack_unwinding::scope_success>(
      		[=]()->void { store_simulation_results(result); }
	);

By using stack_unwinding::scope_action of type stack_unwinding::scope_success you get a strong guarantee (from the compiler) that the function for storing the result of a simulation will only be called if everything went just well (no exception). Besides that, I find the code cleaner and more readable. In similar fashion, we can handle the failure of a simulation. Let’s assume in case of failure we want to create some kind of error report:

//report an error in simulation
stack_unwinding::scope_action at_failure
	= stack_unwinding::make<stack_unwinding::scope_failure>(
		[=]()->void { auto err_report = create_report();
					 store_report(err_report); }
	);

Putting it all in perspective we might end up with a function similar to the following:

void foo_simulation(const sim_data &data) {

	auto metadata = prepare_simulation(data);
	calculate_step1(metadata);
	calculate_step2(metadata);
	auto result = calculate_final_step(metadata);

	//store simulation results 
	stack_unwinding::scope_action at_success
		= stack_unwinding::make<stack_unwinding::scope_success>(
      		[=]()->void { store_simulation_results(result); }
	);

	//report an error in simulation
	stack_unwinding::scope_action at_failure
		= stack_unwinding::make<stack_unwinding::scope_failure>(
			[=]()->void { store_report(create_report()); }
	);	
}

As you can see by using declarative control-flow we end up with nice, simple and readable functions with almost zero effort.

Final notes

I think the obvious benefits of using declarative control-flow are clear to everyone, by now. Besides clean and readable code it also makes the programmer use exceptions instead of old-school return values. It also makes you think in terms of RAII when it comes to structuring your codebase. At the time of writing this blog post, it really seems that the only thing against using declarative control-flow is the fact that it’s not yet a standard in the C++ language (but C++17 is nearby).


Share this: