I am developing a simple macro that enables Python style 'with' in C++.
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
.
The motivation are familiar bugs like these:
void bug1() {
std::unique_lock<std::mutex>(my_mutex);
// oooops, forgot to give the lock a name
function_call();
}
void bug2() {
std::unique_lock<std::mutex> waldo(my_mutex);
function_call();
// oooops, forgot to unlock
other_function_call();
}
void ugly() {
{
// intent of explicit scope not as obvious as could be
std::unique_lock<std::mutex> waldo(my_mutex);
function_call();
}
other_function_call();
}
So here are the questions:
- Is this useful to anyone but me?
- Is this a bad idea for whatever reason?
- Have I overlooked a possibility to implement this in C++03?
- Can it be implemented without the
is_move_constructible
type requirement? - Do you have suggestions for improvement?
- (With a twinkle) Should I apply for some speaking time during the C++Now 2015 Lightning Talks and present this?
And here's my implementation:
NOTE: I have considered an alternative signature such as...
BOOST_WITH(type, ...)
...where ...
would be the arguments to the constructor. I have decided to stick to this:
#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) {
static_assert(std::is_move_constructible<T>::value,
"BOOST_WITH requires the scoped object's type to be move "
"constructible");
return always_true<T>{std::forward<T>(what)};
}
}} // namepace boost::with_detail
#endif // BOOST_WITH_HPP_INCLUDED
Finally, a minimal example:
#include "with.hpp"
#include <cassert>
#include <mutex>
// nothing serious, just a prove of concept
struct Lockable {
Lockable() : locked(false) {}
void lock() { locked = true; }
void unlock() { locked = false; }
bool locked;
};
int main() {
Lockable the_lock;
assert(!the_lock.locked);
BOOST_WITH(std::unique_lock<Lockable>(the_lock))
assert(the_lock.locked);
assert(!the_lock.locked);
}
std::lock_guard()
?. This is probably a better a approach than using a macro. Mots of the time I consider writing macros is a bad idea, and will be hard to get completely right without any side effects. \$\endgroup\$void bug1()
\$\endgroup\$// oooops, forgot to give the lock a name
And the compiler error message isn't pointing out this? \$\endgroup\$