Skip to content

Instantly share code, notes, and snippets.

@foonathan
Last active August 24, 2023 08:42
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save foonathan/023ff0fe923c6b0312dfc15e17ebb595 to your computer and use it in GitHub Desktop.
Save foonathan/023ff0fe923c6b0312dfc15e17ebb595 to your computer and use it in GitHub Desktop.
Quick'n'dirty implementation of Rust's borrow checker for a C++Now Lightning Talk - not supposed to be used
#include <iostream>
#include "borrow_checker.hpp"
int main()
{
auto i = 42;
// borrow `i` under name `ref`
borrow_var(ref, i)
{
// ++i; - error
// *ref = 0; - error
std::cout << *ref << ' ' << i << '\n';
};
// mutable borrow `i` under name `ref`
mut_borrow_var(ref, i)
{
// auto var = i; - error
*ref = 42;
};
++i;
}
#ifndef BORROW_CHECKER_HPP_INCLUDED
#define BORROW_CHECKER_HPP_INCLUDED
#include <initializer_list>
#include <new>
#include <type_traits>
namespace detail
{
template <typename T>
class ref
{
public:
explicit ref(T& obj)
: ptr_(&obj) {}
T& operator*() const noexcept
{
return *ptr_;
}
T* operator->() const noexcept
{
return ptr_;
}
private:
T* ptr_;
};
template <typename Object, typename Expression>
struct borrow_lambda_invoker
{
Object& object;
Expression& expression;
template <typename Lambda>
auto call_lambda(int, const Lambda& l)
-> decltype(l(object, ref<Expression>(expression)))
{
return l(object, ref<Expression>(expression));
}
template <typename Lambda>
auto call_lambda(short, const Lambda& l)
-> decltype(l({}, ref<Expression>(expression)))
{
return l({}, ref<Expression>(expression));
}
template <typename Lambda>
void operator=(const Lambda& l)
{
call_lambda(0, l);
}
};
struct error_variable_borrowed_as_mutable
{
error_variable_borrowed_as_mutable(const error_variable_borrowed_as_mutable&) = delete;
};
template <typename T>
struct destructive_move_holder
{
std::aligned_storage_t<sizeof(T), alignof(T)> storage;
bool should_destroy;
template <typename ... Args>
destructive_move_holder(Args&&... args)
: should_destroy(true)
{
::new(&storage) T(std::forward<Args>(args)...);
}
template <typename U>
destructive_move_holder(std::initializer_list<U> list)
: should_destroy(true)
{
::new(&storage) T(std::move(list));
}
destructive_move_holder(const destructive_move_holder&) = delete;
destructive_move_holder& operator=(const destructive_move_holder&) = delete;
~destructive_move_holder()
{
if (should_destroy)
get().~T();
}
T& get()
{
void* mem = &storage;
return *static_cast<T*>(mem);
}
T&& move()
{
should_destroy = false;
return std::move(get());
}
};
template <typename T>
struct destructive_move_lambda_invoker
{
destructive_move_holder<T> holder;
template <typename ... Args>
destructive_move_lambda_invoker(Args&&... args)
: holder(std::forward<Args>(args)...) {}
template <typename U>
destructive_move_lambda_invoker(std::initializer_list<U> list)
: holder(std::move(list)) {}
template <typename Lambda>
void operator=(const Lambda& lambda)
{
lambda(holder, holder.get());
}
};
}
template <typename T>
using ref = detail::ref<const T>;
template <typename T>
using mut_ref = detail::ref<T>;
template <typename T>
ref<T> borrow(const T& obj)
{
return ref<T>(obj);
}
template <typename T>
mut_ref<T> mut_borrow(T& obj)
{
return mut_ref<T>(obj);
}
#define _borrow_lambda_invoker(Obj, ...) \
detail::borrow_lambda_invoker<std::remove_reference_t<decltype(Obj)>, \
const std::remove_reference_t<decltype(__VA_ARGS__)>>{Obj, __VA_ARGS__}
#define _mut_borrow_lambda_invoker(Obj, ...) \
detail::borrow_lambda_invoker<std::remove_reference_t<decltype(Obj)>, \
std::remove_reference_t<decltype(__VA_ARGS__)>>{Obj, __VA_ARGS__}
#define _borrow_lambda(Name, Obj) \
[&]([[gnu::unused]] const auto& Obj, const auto& Name)
#define _mut_borrow_lambda(Name, Obj) \
[&]([[gnu::unused]] const detail::error_variable_borrowed_as_mutable& Obj, const auto& Name)
#define _borrow(Name, Obj, ...) \
_borrow_lambda_invoker(Obj, __VA_ARGS__) = _borrow_lambda(Name, Obj)
#define _mut_borrow(Name, Obj, ...) \
_mut_borrow_lambda_invoker(Obj, __VA_ARGS__) = _mut_borrow_lambda(Name, Obj)
#define borrow_var(Name, Obj) _borrow(Name, Obj, Obj)
#define borrow_expr(Name, Obj, ...) _borrow(Name, Obj, __VA_ARGS__)
#define borrow_elem(Name, Obj, I) _borrow(Name, Obj, Obj[I])
#define mut_borrow_var(Name, Obj) _mut_borrow(Name, Obj, Obj)
#define mut_borrow_expr(Name, Obj, ...) _mut_borrow(Name, Obj, __VA_ARGS__)
#define mut_borrow_elem(Name, Obj, I) _mut_borrow(Name, Obj, Obj[I])
#define destructive_moveable(Type, Name, ...) \
detail::destructive_move_lambda_invoker<Type>{__VA_ARGS__} \
= [&]([[gnu::unused]] auto& _holder_##Name, auto& Name)
#define destructive_move(Name) \
_holder_##Name.move()
#endif // BORROW_CHECKER_HPP_INCLUDED
#include <iostream>
#include <vector>
#include "borrow_checker.hpp"
void func_borrow(ref<std::vector<int>> vec)
{
for (auto& el : *vec)
std::cout << el << ' ';
std::cout << '\n';
}
void func_mut_borrow(mut_ref<std::vector<int>> vec)
{
vec->push_back(4);
}
void func_destructive_move(std::vector<int> vec)
{
std::cout << "Now it's mine!\n";
}
int main()
{
destructive_moveable(std::vector<int>, vec, {1, 2, 3})
{
vec.push_back(42);
func_borrow(borrow(vec));
func_mut_borrow(mut_borrow(vec));
func_borrow(borrow(vec));
func_destructive_move(destructive_move(vec));
};
}
#include <iostream>
#include <vector>
#include "borrow_checker.hpp"
int main()
{
std::vector<int> vec = {1, 2, 3};
// borrow `vec[0]` as ref, locking `vec`
borrow_expr(ref, vec, vec[0])
{
//*ref = 0; - error
//vec.push_back(4); - error
std::cout << vec.size() << ' ' << *ref << '\n';
};
vec.push_back(4);
}
@fogti
Copy link

fogti commented Aug 20, 2017

Under which license is this code released (for possible reuse, even if it's not supposed to be used)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment