Python style ‘with’ in C++

I have developed a simple macro that enables Python style ‘with’ in C++.

This being said, I feel the urgent need for a short foreword…

  • Yes. ITS_A_MACRO. If you’re already tempted to email me about how bad an idea macros are, go ahead. I can take it. Naturally, I would prefer this to be part of the language.
  • This is not about enabling Python style programming in C++, but about bringing in a nice concept from another language. Think about it: Python is very similar to C++ with respect to resource ownership and management (RAII).

The motivation are familiar bugs like these:

void bug1() {
    std::unique_lock<std::mutex>{the_mutex};
    // oooops, forgot to give the lock a name
    // compiler creates a temporary,
    // whose destructor is called before:
    function_call();
}

void bug2() {
    std::unique_lock<std::mutex> waldo{the_mutex};
    function_call();
    // oooops, forgot to unlock
    // lock outlives this next line:
    other_function_call();
}

void ugly() {
    {
        // intent of explicit scope
        // not as obvious as could be
        // leaves possibility of bug1() problem
        std::unique_lock<std::mutex> waldo{the_mutex};
        function_call();
    }
    other_function_call();
}

There are, of course, several ways to circumvent these bugs. I will not argue that. However, the ‘with’ keyword, shamelessly stolen from Python, would solve these problems while making the code more readable, the intent clearer and the typing work less.

So, if this feature were part of C++, I would expect it to look like this:

void example1() {
    with (std::unique_lock<std::mutex>{the_mutex}) {
        function_call();
        ++i;
    }
    // the_mutex unlocked
    other_function_call();
}

void example2() {
    with (Pushed_matrix{})
        draw_the_scene();
    // matrix popped
}

void example3()
with (Format_switch{the_stream, Format::JSON}) {
    the_stream << whatever;
}
// the_stream returned to previous state

One could argue that it might be desirable to have access to the scoped object by giving it a name. I am not fully convinced, but it would be relatively simple to come up with corresponding syntax (with (auto foo = ...)).

Since we do NOT have the keyword, I came up with a macro solution.

I have shamelessly prefixed the macro name with BOOST_ – primarily because there’s a similarity to BOOST_FOREACH, and because I didn’t want to call it plain WITH.

#ifndef BOOST_WITH_HPP_INCLUDED
#define BOOST_WITH_HPP_INCLUDED

#include <utility>
#include <type_traits>

// This macro expands to code that can be used in the same way as the standard
// control structures can. Whatever 'exp' returns lives as long as a loop
// variable would in similar context.
//
// Example:
// BOOST_WITH(std::unique_lock<std::mutex>(my_mutex))
//     do_something();
//
// Example:
// BOOST_WITH(Pushed_matrix()) {
//     draw_something();
//     draw_something_else();
// }
#define BOOST_WITH(exp)                                                        \
    if (auto BOOST_WITH_always_true = boost::with_detail::make_true(exp))

namespace boost {
namespace with_detail {

// wraps an object of movable type
// and provides conversion to bool (always true)
template <class T>
struct always_true {
    explicit always_true(T what) : x{std::move(what)} {}
    constexpr operator bool() const { return true; }
    T x;
};

// always_true<T> construction helper
template <class T>
always_true<T> make_true(T&& what, std::true_type) {
    static_assert(std::is_move_constructible<T>::value,
                  "this is a BOOST_WITH bug");
    return always_true<T>{std::forward<T>(what)};
}

// static error for non-movable types
template <class T>
std::true_type make_true(T&&, std::false_type) {
    static_assert(std::is_move_constructible<T>::value,
                  "BOOST_WITH requires the scoped object's type to be move "
                  "constructible");
    return std::true_type{}; // never reached
}

// macro entry point, tag dispatch for static type checking
template <class T>
auto make_true(T&& what)
    -> decltype(make_true(std::forward<T>(what),
                          std::is_move_constructible<T>{})) {
    return make_true(std::forward<T>(what), std::is_move_constructible<T>{});
}

}} // namepace boost::with_detail

#endif // BOOST_WITH_HPP_INCLUDED

One thought on “Python style ‘with’ in C++

  1. Pingback: BOOST_WITH Reloaded | 26elf

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>