Skip to content

Instantly share code, notes, and snippets.

@dmaclach
Last active May 19, 2023 20:34
Show Gist options
  • Save dmaclach/afafbeb1418e5d77b505fabfffe96d17 to your computer and use it in GitHub Desktop.
Save dmaclach/afafbeb1418e5d77b505fabfffe96d17 to your computer and use it in GitHub Desktop.
/**
* Wraps C++ classes (ptrs and refs) so that they can be passed around in
* Objective-C++ programs without generating a lot of useless Objective-C
* runtime metadata.
* Use objc_metadata_hider_ptr for ptrs and objc_metadata_hider_ref for refs.
* Example use:
* ```
* class Foo {
* public:
* ....
* };
* @interface Bar
* @property objc_metadata_hider_ptr<Foo> foo;
* - (instancetype)initWithFoo:(objc_metadata_hider_ptr<Foo>)aFoo;
* @end
* ```
* See https://medium.com/@dmaclach/objective-c-encoding-and-you-866624cc02de
* for details.
*
* NOTE: objc_metadata_hiders will by default assert at compile time if having
* the wrapper actually compiles to more metadata than not having the wrapper.
* If you would like to override this you can do it on a per instantiation basis
* you can use `objc_metadata_hider_ptr<Foo, false> foo;` or the equivalent
* `unchecked_objc_metadata_hider_ptr<Foo>`. Note that this change may still
* ripple throughout your codebase like any other type change.
*
*/
#ifndef OBJC_METADATA_HIDER_H_
#define OBJC_METADATA_HIDER_H_
#ifndef __cplusplus
// Swift's Clang importer treats headers as Objective-C when precompiling the
// module. Emit an error on any other attempt to import this within a non-C++
// file.
#ifndef __swift__
#error Must be compiled using (Obj)C++
#endif
#else // __cplusplus
#include <cstddef>
#include <memory>
// NOLINTBEGIN: google-explicit-constructor, whitespace/line_length
namespace gmac {
// ObjCSizeChecker will fail at compile time if the wrapper is being applied
// to a class that would actually have less metadata if it wasn't wrapped.
// See the example at the top of this file for how to control the use of this
// checker on a per-instantiation basis.
template <bool perform_check, size_t unwrapped_size, size_t wrapped_size>
constexpr void ObjCSizeChecker(char const (&)[unwrapped_size], char const (&)[wrapped_size]) {
static_assert(!perform_check || unwrapped_size >= wrapped_size,
"Objective-C metadata size of class wrapped in "
"objc_metadata_hider is greater than or equal to size of "
"unwrapped class. Remove unneeded use of objc_metadata_hider.");
}
// Wraps C++ ptrs so that they can be passed around in Objective-C++ code
// without generating a lot of useless Objective-C runtime metadata.
// `T` is the type being wrapped, `size_check` is whether or not the compiler
// should verify that the wrapped metadata is actually smaller than what would
// occur with unwrapped metadata.
//
// See top of file for example.
template <typename T, bool size_check = true>
class objc_metadata_hider_ptr {
public:
constexpr objc_metadata_hider_ptr() noexcept : p_(nullptr) { SizeCheck(); }
objc_metadata_hider_ptr(T *p) noexcept : p_(p) { SizeCheck(); }
objc_metadata_hider_ptr(const std::unique_ptr<T> &p) noexcept : p_(p.get()) { SizeCheck(); }
objc_metadata_hider_ptr(const std::shared_ptr<T> &p) noexcept : p_(p.get()) { SizeCheck(); }
objc_metadata_hider_ptr(const objc_metadata_hider_ptr<T, true> &p) noexcept : p_(p.get()) {
SizeCheck();
}
objc_metadata_hider_ptr(const objc_metadata_hider_ptr<T, false> &p) noexcept : p_(p.get()) {
SizeCheck();
}
objc_metadata_hider_ptr<T, size_check> &operator=(
const objc_metadata_hider_ptr<T, true> &p) noexcept {
p_ = p.get();
return *this;
}
objc_metadata_hider_ptr<T, size_check> &operator=(
const objc_metadata_hider_ptr<T, false> &p) noexcept {
p_ = p.get();
return *this;
}
objc_metadata_hider_ptr<T, size_check> &operator=(const std::unique_ptr<T> &p) noexcept {
p_ = p.get();
return *this;
}
objc_metadata_hider_ptr<T, size_check> &operator=(const std::shared_ptr<T> &p) noexcept {
p_ = p.get();
return *this;
}
objc_metadata_hider_ptr<T, size_check> &operator=(T *p) noexcept {
p_ = p;
return *this;
}
bool operator==(const objc_metadata_hider_ptr<T, true> &p) const noexcept {
return p_ == p.get();
}
bool operator==(const objc_metadata_hider_ptr<T, false> &p) const noexcept {
return p_ == p.get();
}
bool operator!=(const objc_metadata_hider_ptr<T, true> &p) const noexcept {
return p_ != p.get();
}
bool operator!=(const objc_metadata_hider_ptr<T, false> &p) const noexcept {
return p_ != p.get();
}
bool operator==(const std::unique_ptr<T> &p) const noexcept { return p_ == p.get(); }
bool operator==(const std::shared_ptr<T> &p) const noexcept { return p_ == p.get(); }
bool operator==(const T *p) const noexcept { return p_ == p; }
bool operator==(const std::nullptr_t) const noexcept { return p_ == nullptr; }
explicit operator bool() const noexcept { return p_ != nullptr; }
T &operator*() noexcept { return *p_; }
const T &operator*() const noexcept { return *p_; }
T *operator->() noexcept { return p_; }
const T *operator->() const noexcept { return p_; }
T *get() const noexcept { return p_; }
void reset(T *p = nullptr) noexcept { p_ = p; }
private:
T *p_;
constexpr void SizeCheck() {
#if __OBJC__
ObjCSizeChecker<size_check>(@encode(T), @encode(objc_metadata_hider_ptr<T>));
#endif // __OBJC__
}
};
// A type that makes it clear that you are explicitly not checking sizes at
// compile time.
template <typename T>
using unchecked_objc_metadata_hider_ptr = objc_metadata_hider_ptr<T, false>;
// Wraps C++ references so that they can be passed around in Objective-C++
// code without generating a lot of useless Objective-C runtime metadata.
// `T` is the type being wrapped, `size_check` is whether or not the compiler
// should verify that the wrapped metadata is actually smaller than what would
// occur with unwrapped metadata.
//
// See top of file for example.
template <typename T, bool size_check = true>
class objc_metadata_hider_ref {
public:
objc_metadata_hider_ref() = delete;
objc_metadata_hider_ref(T &p) noexcept : p_(p) { SizeCheck(); }
objc_metadata_hider_ref<T, size_check> &operator=(const T &p) noexcept {
p_ = p;
return *this;
}
bool operator==(const objc_metadata_hider_ref<T, size_check> &p) const noexcept {
return p_ == p.p_;
}
bool operator==(const T &p) const noexcept { return p_ == p; }
operator T &() noexcept { return p_; }
private:
T &p_;
constexpr void SizeCheck() {
#if __OBJC__
ObjCSizeChecker<size_check>(@encode(T), @encode(objc_metadata_hider_ref<T>));
#endif // __OBJC__
}
};
// A type that makes it clear that you are explicitly not checking sizes at
// compile time.
template <typename T>
using unchecked_objc_metadata_hider_ref = objc_metadata_hider_ref<T, false>;
} // namespace gmac
// NOLINTEND: google-explicit-constructor, whitespace/line_length
#endif // __cplusplus
#endif // OBJC_METADATA_HIDER_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment