-
-
Save Konrad1991/2556eab8e3f77a0148234226005db0dc 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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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)); | |
}; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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