Skip to content

Instantly share code, notes, and snippets.

@Happy-Ferret
Forked from foonathan/borrow.cpp
Created May 2, 2022 22:20
Show Gist options
  • Save Happy-Ferret/1a66e2670a6d13a92394e3ff021a1df9 to your computer and use it in GitHub Desktop.
Save Happy-Ferret/1a66e2670a6d13a92394e3ff021a1df9 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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment