Files
tdesktop/cmake/external/glib/cppgir/gi/container.hpp
allhaileris afb81b8278
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
init
2026-02-16 15:50:16 +03:00

1971 lines
56 KiB
C++

#ifndef GI_CONTAINER_HPP
#define GI_CONTAINER_HPP
#include "base.hpp"
#include "wrap.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
// helpers
// element unwrap cast helper;
// element reference type depends on container reference type
// lvalue
template<typename T>
T
cast_ref(T &&el, std::true_type)
{
return el;
}
// rvalue
template<typename T>
typename std::remove_reference<T>::type &&
cast_ref(T &&el, std::false_type)
{
return std::move(el);
}
// std::iterator may be deprecated, but it is still handy
// so replicate it here to avoid deprecation warnings
template<typename Category, typename T, typename Distance = std::ptrdiff_t,
typename Pointer = T *, typename Reference = T &>
struct iterator
{
using iterator_category = Category;
using value_type = T;
using difference_type = Distance;
using pointer = Pointer;
using reference = Reference;
};
namespace _traits
{
template<typename T>
struct hash
{};
template<typename T>
struct equal
{};
template<typename CType>
void
_destroy(CType v)
{
// destroy by wrapping a temporary with full transfer
// and then simply dropping that temporary
wrap(v, transfer_full);
}
// C type case
template<typename CType, typename Enable = void>
struct destroy
{
static constexpr auto func = _destroy<CType>;
};
// Cpp type case
template<typename CppType>
struct destroy<CppType, typename traits::if_valid_type<typename traits::ctype<
typename std::decay<CppType>::type>::type>::type>
{
using CType =
typename traits::ctype<typename std::decay<CppType>::type>::type;
static constexpr auto func = _destroy<CType>;
};
// let's simply stick to the following for now
// since others are not so likely and not always clear semantics
template<>
struct hash<std::string>
{
static constexpr GHashFunc func = g_str_hash;
};
template<>
struct hash<gi::cstring>
{
static constexpr GHashFunc func = g_str_hash;
};
template<>
struct hash<gi::cstring_v>
{
static constexpr GHashFunc func = g_str_hash;
};
template<>
struct equal<std::string>
{
static constexpr GEqualFunc func = g_str_equal;
};
template<>
struct equal<gi::cstring>
{
static constexpr GEqualFunc func = g_str_equal;
};
template<>
struct equal<gi::cstring_v>
{
static constexpr GEqualFunc func = g_str_equal;
};
} // namespace _traits
/////////////////
//
////////////////
// helper check/trait
namespace trait
{
std::false_type is_initializer_list_dispatch(...);
template<typename T>
std::true_type is_initializer_list_dispatch(std::initializer_list<T> *);
template<typename T>
struct is_initializer_list
: decltype(is_initializer_list_dispatch(static_cast<T *>(nullptr)))
{};
} // namespace trait
// helper; combine some traits
template<typename T, typename Transfer>
using elcpptype = traits::cpptype<T, typename element_transfer<Transfer>::type>;
// hard convert/cast of a pointer to a C type to its wrapper type
// (which is expected of same size)
template<typename Transfer, typename CType,
typename CppType = typename elcpptype<CType, Transfer>::type>
CppType *
wrap_cast(CType *c)
{
static_assert(sizeof(*c) == sizeof(std::declval<CppType>()), "");
return reinterpret_cast<CppType *>(c);
}
// snippet to disable a method for transfer none case
#define GI_DISABLE_METHOD_NONE \
template<typename Enable = void, \
typename Check = typename std::enable_if< \
std::is_void<Enable>::value && \
!std::is_same<Transfer, transfer_none_t>::value>::type>
template<typename ListType, typename ElType, typename Transfer,
typename Enable = void>
class CollectionBase
{};
template<typename ListType, typename ElCType, typename Transfer>
struct list_ops;
struct GPtrArrayFuncs
{
constexpr static bool refcnt = true;
using value_type = gpointer;
static GType get_type_() { return G_TYPE_PTR_ARRAY; }
static ::GPtrArray *ref(::GPtrArray *data)
{
return data ? g_ptr_array_ref(data) : data;
}
static ::GPtrArray *sink(::GPtrArray *data)
{
return data ? g_ptr_array_ref(data) : data;
}
static void free(::GPtrArray *&data)
{
if (data) {
g_ptr_array_unref(data);
data = nullptr;
}
}
static ::GPtrArray *float_(::GPtrArray *data) { return data; }
};
template<typename ElCType, typename Transfer>
struct list_ops<::GPtrArray, ElCType, Transfer> : public GPtrArrayFuncs
{};
template<typename ElCType, typename Transfer>
class CollectionBase<::GPtrArray, ElCType, Transfer>
: public Wrapper<GPtrArrayFuncs, ::GPtrArray>
{
protected:
using list_ops = GPtrArrayFuncs;
using state_type = int; // dummy
state_type _create()
{
if (!this->data_) {
auto &l = this->data_;
l = g_ptr_array_new();
if (std::is_same<Transfer, transfer_full_t>::value)
g_ptr_array_set_free_func(
l, (GDestroyNotify)_traits::destroy<ElCType>::func);
}
return 0;
}
void _finish() {}
state_type _push(state_type s, list_ops::value_type item)
{
g_ptr_array_add(this->data_, item);
return s;
}
struct iterator_type
{
CollectionBase *self;
int index;
bool next(list_ops::value_type &val)
{
if (self->data_ && (index < (int)self->data_->len)) {
val = g_ptr_array_index(self->data_, index++);
return true;
}
return false;
}
};
iterator_type _iterator() { return iterator_type{this, 0}; }
void _steal()
{
#if GLIB_CHECK_VERSION(2, 64, 0)
g_free(g_ptr_array_steal(this->data_, nullptr));
#else
// nasty, essentially above function
// but works for code of the past, which no longer changes
auto parray = (GPtrArray *)this->data_;
g_free(parray->pdata);
parray->pdata = 0;
parray->len = 0;
#endif
g_ptr_array_unref(this->data_);
this->data_ = nullptr;
}
public:
using value_type = typename elcpptype<ElCType, Transfer>::type;
protected:
value_type *_cast() const
{
return wrap_cast<Transfer>((ElCType *)(this->data_->pdata));
}
GI_DISABLE_METHOD_NONE
void _ensure_array() { _create(); }
public:
// assume same layout of wrapper and wrappee
static_assert(sizeof(value_type) == sizeof(gpointer), "");
static_assert(sizeof(value_type[2]) == sizeof(gpointer[2]), "");
using iterator = value_type *;
using const_iterator = const value_type *;
const_iterator cbegin() const { return this->data_ ? _cast() : nullptr; }
const_iterator cend() const
{
return this->data_ ? begin() + this->data_->len : nullptr;
}
const_iterator begin() const { return cbegin(); }
const_iterator end() const { return cend(); }
GI_DISABLE_METHOD_NONE
iterator begin() { return this->data_ ? _cast() : nullptr; }
GI_DISABLE_METHOD_NONE iterator end()
{
return this->data_ ? begin() + this->data_->len : nullptr;
}
size_t size() const noexcept { return this->data_ ? this->data_->len : 0; }
bool empty() const noexcept { return size() == 0; }
GI_DISABLE_METHOD_NONE
void push_front(const value_type &v) { insert(begin(), v); }
GI_DISABLE_METHOD_NONE
void push_front(value_type &&v) { insert(begin(), std::move(v)); }
GI_DISABLE_METHOD_NONE
void pop_front() { erase(begin()); }
GI_DISABLE_METHOD_NONE
void push_back(const value_type &v) { insert(end(), v); }
GI_DISABLE_METHOD_NONE
void push_back(value_type &&v) { insert(end(), std::move(v)); }
GI_DISABLE_METHOD_NONE
void pop_back()
{
if (!empty())
erase(end() - 1);
}
GI_DISABLE_METHOD_NONE
iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
GI_DISABLE_METHOD_NONE
iterator erase(const_iterator first, const_iterator last)
{
if (this->data_ && first < last) {
auto index = first - begin();
auto cnt = last - first;
// erase from back to front to minimize (or even avoid) data moves
while (cnt) {
--cnt;
auto v = g_ptr_array_steal_index(this->data_, index + cnt);
_traits::destroy<ElCType>::func(ElCType(v));
}
return begin() + index;
}
return begin();
}
GI_DISABLE_METHOD_NONE
iterator insert(const_iterator pos, const value_type &v)
{
auto cv = unwrap(v, Transfer());
return _insert(pos, cv);
}
GI_DISABLE_METHOD_NONE
iterator insert(const_iterator pos, value_type &&v)
{
auto cv = unwrap(std::move(v), Transfer());
return _insert(pos, cv);
}
// erase to avoid memory free
GI_DISABLE_METHOD_NONE
void clear() { erase(begin(), end()); }
protected:
iterator _insert(const_iterator pos, gpointer v)
{
// establish index before potential create
auto index = pos - begin();
_ensure_array();
g_ptr_array_insert(this->data_, index, v);
return begin() + index;
}
};
struct GHashTableFuncs
{
constexpr static bool refcnt = true;
using value_type = std::pair<gpointer, gpointer>;
static GType get_type_() { return G_TYPE_HASH_TABLE; }
static ::GHashTable *ref(::GHashTable *data)
{
return data ? g_hash_table_ref(data) : data;
}
static ::GHashTable *sink(::GHashTable *data)
{
return data ? g_hash_table_ref(data) : data;
}
static void free(::GHashTable *&data)
{
if (data) {
g_hash_table_unref(data);
data = nullptr;
}
}
static ::GHashTable *float_(::GHashTable *data) { return data; }
};
template<typename ElCType, typename Transfer>
struct list_ops<::GHashTable, ElCType, Transfer> : public GHashTableFuncs
{};
template<typename ElCType, typename Transfer>
class CollectionBase<::GHashTable, ElCType, Transfer>
: public Wrapper<GHashTableFuncs, ::GHashTable>
{
protected:
using list_ops = GHashTableFuncs;
using ElCKeyType = typename std::tuple_element<0, ElCType>::type;
using ElCMappedType = typename std::tuple_element<1, ElCType>::type;
using ElCppKeyType = typename elcpptype<ElCKeyType, Transfer>::type;
using ElCppMappedType = typename elcpptype<ElCMappedType, Transfer>::type;
static_assert(sizeof(ElCKeyType) == sizeof(gpointer), "");
static_assert(sizeof(ElCMappedType) == sizeof(gpointer), "");
using key_type = const ElCppKeyType;
using mapped_type = ElCppMappedType;
using value_type = std::pair<ElCppKeyType, mapped_type>;
using const_value_type = std::pair<const ElCppKeyType, const mapped_type>;
using state_type = int; // dummy
state_type _create()
{
if (!this->data_) {
if (std::is_same<Transfer, transfer_full_t>::value) {
this->data_ = g_hash_table_new_full(_traits::hash<ElCppKeyType>::func,
_traits::equal<ElCppKeyType>::func,
GDestroyNotify(_traits::destroy<ElCppKeyType>::func),
GDestroyNotify(_traits::destroy<ElCppMappedType>::func));
} else {
this->data_ = g_hash_table_new(_traits::hash<ElCppKeyType>::func,
_traits::equal<ElCppKeyType>::func);
}
}
return 0;
}
state_type _push(state_type s, list_ops::value_type item)
{
g_hash_table_replace(this->data_, item.first, item.second);
return s;
}
void _finish() {}
struct iterator_type
{
CollectionBase &self;
GHashTableIter iter;
int index{};
iterator_type(CollectionBase &_self) : self(_self)
{
if (self.data_)
g_hash_table_iter_init(&iter, self.data_);
}
bool next(list_ops::value_type &val)
{
if (!self.data_)
return false;
gpointer key, value;
auto more = g_hash_table_iter_next(&iter, &key, &value);
val = {key, value};
return more;
}
};
iterator_type _iterator() { return iterator_type{*this}; }
void _steal()
{
g_hash_table_steal_all(this->data_);
g_hash_table_unref(this->data_);
this->data_ = nullptr;
}
public:
class const_iterator
: public detail::iterator<std::input_iterator_tag, const_value_type>
{
using self_type = const_iterator;
enum State { NONE, INIT, ITERATING, DONE };
::GHashTableIter iter;
std::pair<gpointer, gpointer> kv;
State state;
const_value_type *_cast()
{
// sanity check in view of rough cast
static_assert(sizeof(kv) == sizeof(const_value_type), "");
return reinterpret_cast<const_value_type *>(&kv);
}
::GHashTable *_table() const
{
return state == NONE || state == DONE
? nullptr
: g_hash_table_iter_get_hash_table(
const_cast<::GHashTableIter *>(&iter));
}
public:
const_iterator(
::GHashTable *table, gpointer key = nullptr, gpointer value = nullptr)
: state(NONE)
{
if (table) {
g_hash_table_iter_init(&iter, table);
state = INIT;
// if iterator is created as a result of a find,
// then we arrange for lazy sync of hash table iterator
if (G_LIKELY(!key && !value)) {
state = ITERATING;
++(*this);
} else {
kv = {key, value};
}
}
}
const_value_type &operator*() { return *_cast(); }
const_value_type *operator->() { return _cast(); }
bool operator==(const self_type &other)
{
auto t = _table();
return (t == other._table()) && (!t || (kv.first == other.kv.first));
}
bool operator!=(const self_type &other) { return !(*this == other); }
self_type &operator++()
{
bool has_more = true;
// if needed, sync iterator with key
if (G_UNLIKELY(state != ITERATING)) {
gpointer key{}, value{};
while (has_more && key != kv.first) {
has_more = g_hash_table_iter_next(&iter, &key, &value);
}
}
if (G_LIKELY(has_more))
has_more = g_hash_table_iter_next(&iter, &kv.first, &kv.second);
state = has_more ? ITERATING : DONE;
return *this;
}
};
// consistency
using iterator = const_iterator;
const_iterator cbegin() const { return {this->data_}; }
const_iterator cend() const { return {nullptr}; }
const_iterator begin() const { return cbegin(); };
const_iterator end() const { return cend(); };
size_t size() const
{
return this->data_ ? g_hash_table_size(this->data_) : 0;
}
bool empty() const { return size() == 0; }
GI_DISABLE_METHOD_NONE
void clear()
{
if (this->data_)
g_hash_table_remove_all(this->data_);
};
size_t count(key_type &key) const
{
if (!this->data_)
return 0;
auto v = unwrap(key, transfer_none);
return g_hash_table_contains(this->data_, v) ? 1 : 0;
}
const_iterator find(key_type &key) const
{
if (this->data_) {
auto v = unwrap(key, transfer_none);
gpointer lv;
if (g_hash_table_lookup_extended(this->data_, v, nullptr, &lv))
return {this->data_, gpointer(v), gpointer(lv)};
}
return end();
}
typename elcpptype<ElCMappedType, transfer_none_t>::type lookup(
key_type &key) const
{
auto it = find(key);
if (it == end())
return {};
// decay copy should be possible
return it->second;
}
GI_DISABLE_METHOD_NONE
bool replace(ElCppKeyType &&key, ElCppMappedType &&value)
{
_create();
auto kv = unwrap(std::move(key), Transfer());
auto vv = unwrap(std::move(value), Transfer());
return g_hash_table_replace(this->data_, kv, vv);
}
GI_DISABLE_METHOD_NONE
size_t erase(key_type &key)
{
if (this->data_) {
auto v = unwrap(key, transfer_none);
return g_hash_table_remove(this->data_, gpointer(v)) ? 1 : 0;
}
return 0;
}
};
template<typename ListType>
struct ListFuncs
{
constexpr static bool refcnt = false;
using value_type = gpointer;
// this value type is used in e.g. signal argument
static GType get_type_() { return G_TYPE_POINTER; }
static ListType *ref(ListType *data) { return data; }
};
template<typename ElCType, typename Transfer>
struct list_ops<::GList, ElCType, Transfer> : public ListFuncs<::GList>
{};
template<typename ElCType, typename Transfer>
struct list_ops<::GSList, ElCType, Transfer> : public ListFuncs<::GSList>
{};
template<typename ListType>
struct LinkedListOps;
template<>
struct LinkedListOps<::GSList>
{
static void free(::GSList *l) { g_slist_free(l); }
static void free_full(::GSList *l, GDestroyNotify func)
{
g_slist_free_full(l, func);
}
static ::GSList *append(::GSList *l, gpointer data)
{
return g_slist_append(l, data);
}
static ::GSList *prepend(::GSList *l, gpointer data)
{
return g_slist_prepend(l, data);
}
static ::GSList *last(::GSList *l) { return g_slist_last(l); }
static ::GSList *insert_before(::GSList *l, ::GSList *link, gpointer data)
{
return g_slist_insert_before(l, link, data);
}
static ::GSList *delete_link(::GSList *l, ::GSList *link)
{
return g_slist_delete_link(l, link);
}
static ::GSList *reverse(::GSList *l) { return g_slist_reverse(l); }
};
template<>
struct LinkedListOps<::GList>
{
static void free(::GList *l) { g_list_free(l); }
static void free_full(::GList *l, GDestroyNotify func)
{
return g_list_free_full(l, func);
}
static ::GList *append(::GList *l, gpointer data)
{
return g_list_append(l, data);
}
static ::GList *prepend(::GList *l, gpointer data)
{
return g_list_prepend(l, data);
}
static ::GList *last(::GList *l) { return g_list_last(l); }
static ::GList *insert_before(::GList *l, ::GList *link, gpointer data)
{
return g_list_insert_before(l, link, data);
}
static ::GList *delete_link(::GList *l, ::GList *link)
{
return g_list_delete_link(l, link);
}
static ::GList *reverse(::GList *l) { return g_list_reverse(l); }
};
template<typename ListType, typename ElType, typename Transfer>
struct LinkedListBase;
template<typename ListType, typename ElCType>
struct LinkedListBase<ListType, ElCType, transfer_none_t>
: public SharedWrapper<ListType>
{
protected:
static void _deleter(ListType *&d, ...) { d = nullptr; }
static ListType *_copy(ListType *d) { return d; }
};
template<typename ListType, typename ElCType>
struct LinkedListBase<ListType, ElCType, transfer_container_t>
: public SharedWrapper<ListType>
{
protected:
static void _deleter(ListType *&l, ...)
{
LinkedListOps<ListType>::free(l);
l = nullptr;
}
};
template<typename ListType, typename ElCType>
struct LinkedListBase<ListType, ElCType, transfer_full_t>
: public SharedWrapper<ListType>
{
protected:
static void _deleter(ListType *&l, ...)
{
LinkedListOps<ListType>::free_full(
l, (GDestroyNotify)_traits::destroy<ElCType>::func);
l = nullptr;
}
};
// enable copy constructor on none transfer cases
// compiler otherwise always selects the deleted one, even if other options
template<typename Transfer, typename Base>
using SelectBaseWrapper =
typename std::conditional<std::is_same<Transfer, transfer_none_t>::value,
CopyWrapper<Base>, MoveWrapper<Base>>::type;
template<typename ListType, typename ElCType, typename Transfer>
class CollectionBase<ListType, ElCType, Transfer,
typename std::enable_if<std::is_same<ListType, ::GSList>::value ||
std::is_same<ListType, ::GList>::value>::type>
: public SelectBaseWrapper<Transfer,
LinkedListBase<ListType, ElCType, Transfer>>
{
protected:
using list_ops = ListFuncs<ListType>;
using state_type = ListType *;
state_type _create() { return nullptr; }
state_type _push(state_type, typename list_ops::value_type item)
{
this->data_ = LinkedListOps<ListType>::prepend(this->data_, item);
return nullptr;
}
void _finish()
{
this->data_ = LinkedListOps<ListType>::reverse(this->data_);
}
struct iterator_type
{
ListType *it;
bool next(typename list_ops::value_type &val)
{
if (it) {
val = it->data;
it = it->next;
return true;
}
return false;
}
};
iterator_type _iterator() { return {this->data_}; }
void _steal()
{
LinkedListOps<ListType>::free(this->data_);
this->data_ = nullptr;
}
static void _destroy(gpointer value)
{
_traits::destroy<ElCType>::func(ElCType(value));
}
public:
using value_type = typename elcpptype<ElCType, Transfer>::type;
static constexpr bool is_glist = std::is_same<ListType, ::GList>::value;
template<typename value_t>
class iterator_t : public detail::iterator<std::forward_iterator_tag, value_t>
{
using self_type = iterator_t;
friend class CollectionBase;
ListType *it = nullptr;
public:
iterator_t(ListType *l = nullptr) : it(l){};
// construct const from non-const
template<typename other_value_t,
typename Enable = typename std::enable_if<
std::is_convertible<other_value_t *, value_t *>::value>::type>
iterator_t(const iterator_t<other_value_t> &oi) : iterator_t(oi.it)
{}
value_t &operator*() { return *operator->(); }
value_t *operator->()
{
return wrap_cast<Transfer>((ElCType *)(&it->data));
}
bool operator==(const self_type &other) { return it == other.it; }
bool operator!=(const self_type &other) { return !(*this == other); }
self_type &operator++()
{
if (it)
it = it->next;
return *this;
}
self_type operator++(int)
{
auto ret = *this;
++(*this);
return ret;
}
};
using const_iterator = iterator_t<const value_type>;
using iterator = iterator_t<value_type>;
const_iterator begin() const { return cbegin(); }
const_iterator end() const { return cend(); }
const_iterator cbegin() const { return {this->data_}; }
const_iterator cend() const { return {}; }
GI_DISABLE_METHOD_NONE
iterator begin() { return {this->data_}; }
GI_DISABLE_METHOD_NONE
iterator end() { return {}; }
bool empty() const noexcept { return !this->data_; }
GI_DISABLE_METHOD_NONE
void push_front(const value_type &v)
{
auto cv = unwrap(v, Transfer());
this->data_ = LinkedListOps<ListType>::prepend(this->data_, cv);
}
GI_DISABLE_METHOD_NONE
void push_front(value_type &&v)
{
auto cv = unwrap(std::move(v), Transfer());
this->data_ = LinkedListOps<ListType>::prepend(this->data_, cv);
}
GI_DISABLE_METHOD_NONE
void pop_front()
{
if (this->data_)
_destroy(this->data_->data);
this->data_ =
LinkedListOps<ListType>::delete_link(this->data_, this->data_);
}
GI_DISABLE_METHOD_NONE
void push_back(const value_type &v)
{
auto cv = unwrap(v, Transfer());
this->data_ = LinkedListOps<ListType>::append(this->data_, cv);
}
GI_DISABLE_METHOD_NONE
void push_back(value_type &&v)
{
auto cv = unwrap(std::move(v), Transfer());
this->data_ = LinkedListOps<ListType>::append(this->data_, cv);
}
GI_DISABLE_METHOD_NONE
void pop_back()
{
auto last = LinkedListOps<ListType>::last(this->data_);
if (last)
_destroy(last->data);
this->data_ = LinkedListOps<ListType>::delete_link(this->data_, last);
}
GI_DISABLE_METHOD_NONE
iterator erase(const_iterator pos)
{
ListType *next = nullptr;
if (pos.it) {
next = pos.it->next;
_destroy(pos.it->data);
}
this->data_ = LinkedListOps<ListType>::delete_link(this->data_, pos.it);
return {next};
}
template<typename Enable = void,
typename Check = typename std::enable_if<
std::is_void<Enable>::value && Transfer().value && is_glist>::type>
iterator insert(const_iterator pos, const value_type &v)
{
auto cv = unwrap(v, Transfer());
return _insert(pos, cv);
}
template<typename Enable = void,
typename Check = typename std::enable_if<
std::is_void<Enable>::value && Transfer().value && is_glist>::type>
iterator insert(const_iterator pos, value_type &&v)
{
auto cv = unwrap(std::move(v), Transfer());
return _insert(pos, cv);
}
GI_DISABLE_METHOD_NONE
void reverse()
{
this->data_ = LinkedListOps<ListType>::reverse(this->data_);
}
GI_DISABLE_METHOD_NONE
void clear()
{
this->_deleter(this->data_);
this->data_ = nullptr;
}
protected:
iterator _insert(const_iterator pos, gpointer v)
{
this->data_ =
LinkedListOps<ListType>::insert_before(this->data_, pos.it, v);
return {pos.it ? pos.it->prev : LinkedListOps<ListType>::last(this->data_)};
}
};
enum SpanType {
DYNAMIC = -1,
ZT = 0,
};
template<int SIZE>
struct Span
{};
template<typename ElCType, typename Transfer, int SIZE>
struct SpanBase : public SharedWrapper<ElCType>
{
protected:
// extra members needed in this case
// only > 0 if data locally allocated (as opposed to from elsewhere)
size_t capacity_ = 0;
size_t size_ = 0;
static void _deleter(decltype(SpanBase::data_) &d, SpanBase *self)
{
assert(self);
if (std::is_same<Transfer, transfer_full_t>::value && d) {
auto end = d + self->size_;
for (auto p = d; p != end; ++p) {
_traits::destroy<ElCType>::func(*p);
}
}
if (!std::is_same<Transfer, transfer_none_t>::value)
g_free(d);
d = nullptr;
}
template<typename Enable = void>
static decltype(SpanBase::data_) _copy(decltype(SpanBase::data_) d)
{
// should only end up used in none case
static_assert(std::is_same<Transfer, transfer_none_t>::value, "");
return d;
}
};
template<typename ElCType, typename Transfer, int SIZE>
struct list_ops<Span<SIZE>, ElCType, Transfer> : public ListFuncs<ElCType>
{};
template<typename ElCType, typename Transfer, int SIZE>
class CollectionBase<Span<SIZE>, ElCType, Transfer>
: public SelectBaseWrapper<Transfer, SpanBase<ElCType, Transfer, SIZE>>
{
protected:
static auto constexpr SPAN_SIZE = SIZE;
struct list_ops : public ListFuncs<ElCType>
{
using value_type = ElCType;
};
using state_type = ElCType *;
// used by wrap
CollectionBase(ElCType *p = nullptr, int s = 0)
{
// NOTE no copy needed;
// either transfer is full/container (and ownership is assumed)
// either transfer is none (and will never free/release)
this->data_ = p;
this->capacity_ = 0;
// normalize -1 from C side to avoid potential surprises elsewhere
if (s < 0 || SIZE == ZT) {
s = 0;
if (p) {
while (*p) {
++s;
++p;
}
}
}
this->size_ = s;
}
ElCType *_create()
{
if (!this->data_) {
this->capacity_ = SIZE > 0 ? SIZE : 2;
this->data_ = (ElCType *)g_malloc0(this->capacity_ * sizeof(ElCType));
this->size_ = 0;
}
return this->data_;
}
void _create_from(ElCType *d, size_t lsize)
{
using T = ElCType;
// try to re-use incoming data
// otherwise fall back to plain copy
// (if transfer or zero-termination fiddling is needed)
bool allocate = (Transfer().value != transfer_none.value) || (SIZE == ZT);
auto size = SIZE == ZT ? lsize + 1 : lsize;
auto *data = d;
if (allocate) {
data = (T *)g_malloc(size * sizeof(T));
this->capacity_ = size;
memcpy(data, d, lsize * sizeof(T));
if (SIZE == ZT)
data[lsize] = T{};
}
// track data
assert(!this->data_);
g_free(this->data_);
this->data_ = data;
this->size_ = lsize;
}
void _reserve(size_t capacity)
{
if (this->capacity_ < capacity + (SIZE == ZT)) {
this->capacity_ =
std::max(std::max(2 * this->capacity_, size_t(2)), capacity);
this->data_ =
(ElCType *)g_realloc_n(this->data_, this->capacity_, sizeof(ElCType));
}
}
state_type _push(state_type s, ElCType item)
{
// should only get here if previously allocated
assert(this->capacity_ > 0);
auto offset = s - this->data_;
_reserve(offset + 1);
s = this->data_ + offset;
*s = item;
++this->size_;
++s;
// ensure ZT if applicable
if (SIZE == ZT) {
assert(size_t(s - this->data_) < this->capacity_);
*s = ElCType{};
}
return s;
}
void _finish()
{
if ((SIZE > 0) && (this->size_ != (size_t)SIZE))
g_error("fixed list size mismatch");
}
struct iterator_type
{
CollectionBase &self;
ElCType *p;
iterator_type(CollectionBase &s) : self(s), p(self.data_) {}
explicit operator bool()
{
return p && (ZT ? !!*p : (p != self.data_ + self.size_));
};
bool next(ElCType &val)
{
if (*this) {
val = *(p++);
return true;
}
return false;
}
};
iterator_type _iterator() { return {*this}; }
void _steal()
{
// if Holder is trying to destroy, check if we really allocated
// (may have optimized array case)
if (!std::is_same<Transfer, transfer_none_t>::value || this->capacity_)
g_free(this->data_);
this->data_ = nullptr;
this->size_ = this->capacity_ = 0;
};
public:
// this one is used by code generation (the unwrap size part)
// so it should only be used in specific situations
// let's add checks to make it so
size_t _size() const
{
static_assert(SIZE != ZT, "");
return this->size_;
}
using value_type = typename elcpptype<ElCType, Transfer>::type;
// assume same layout of wrapper and wrappee
static_assert(sizeof(value_type) == sizeof(ElCType), "");
static_assert(sizeof(value_type[2]) == sizeof(ElCType[2]), "");
using iterator = value_type *;
using const_iterator = const value_type *;
const_iterator cbegin() const
{
return this->data_ ? wrap_cast<Transfer>(this->data_) : nullptr;
}
const_iterator cend() const
{
return this->data_ ? begin() + this->size_ : nullptr;
}
const_iterator begin() const { return cbegin(); }
const_iterator end() const { return cend(); }
GI_DISABLE_METHOD_NONE
iterator begin()
{
return this->data_ ? wrap_cast<Transfer>(this->data_) : nullptr;
}
GI_DISABLE_METHOD_NONE
iterator end() { return this->data_ ? begin() + this->size_ : nullptr; }
size_t size() const { return this->size_; }
bool empty() const { return size() == 0; }
GI_DISABLE_METHOD_NONE
void push_front(const value_type &v) { insert(begin(), v); }
GI_DISABLE_METHOD_NONE
void push_front(value_type &&v) { insert(begin(), std::move(v)); }
GI_DISABLE_METHOD_NONE
void pop_front() { erase(begin()); }
GI_DISABLE_METHOD_NONE
void push_back(const value_type &v) { insert(end(), v); }
GI_DISABLE_METHOD_NONE
void push_back(value_type &&v) { insert(end(), std::move(v)); }
GI_DISABLE_METHOD_NONE
void pop_back()
{
if (!empty())
erase(end() - 1);
}
GI_DISABLE_METHOD_NONE
iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
GI_DISABLE_METHOD_NONE
iterator erase(const_iterator first, const_iterator last)
{
if (this->data_ && this->size_ > 0 && first < last) {
size_t index = first - begin();
size_t cnt = last - first;
size_t lindex = std::min(index + cnt, this->size_);
for (auto i = index; i < lindex; ++i)
_traits::destroy<ElCType>::func(this->data_[index]);
if (lindex < this->size_)
memmove(this->data_ + index, this->data_ + lindex,
sizeof(*this->data_) * (this->size_ - lindex));
this->size_ -= cnt;
if (SIZE == ZT)
this->data_[this->size_] = ElCType{};
return begin() + index;
}
return begin();
}
GI_DISABLE_METHOD_NONE
iterator insert(const_iterator pos, const value_type &v)
{
auto cv = unwrap(v, Transfer());
return _insert(pos, cv);
}
GI_DISABLE_METHOD_NONE
iterator insert(const_iterator pos, value_type &&v)
{
auto cv = unwrap(std::move(v), Transfer());
return _insert(pos, cv);
}
size_t capacity() const { return this->capacity_; }
GI_DISABLE_METHOD_NONE
void reserve(size_t capacity) { _reserve(capacity); }
// erase to avoid memory free
GI_DISABLE_METHOD_NONE
void clear() { erase(begin(), end()); }
protected:
iterator _insert(const_iterator pos, ElCType v)
{
// establish index before potential create
size_t index = pos - begin();
// grow if needed
_reserve(this->size_ + 1);
// move if needed
if (index < this->size_)
memmove(this->data_ + index + 1, this->data_ + index,
sizeof(*this->data_) * (this->size_ - index));
// insert
++this->size_;
this->data_[index] = v;
// ensure ZT if applicable
if (SIZE == ZT) {
assert(this->size_ < this->capacity_);
this->data_[this->size_] = ElCType{};
}
return begin() + index;
}
};
// class tag
class Container
{};
// minor helper, essentially compile-time signal/notification
template<typename T>
struct Notifier
{
static constexpr bool construct_none = false;
static void _updated(T *, bool /*create*/) {}
};
// considered owning for container or full
// unwrap will have to release_()
template<typename ListType, typename T, typename Transfer = transfer_full_t,
typename Notify = Notifier<void>, typename ExtraBase = Container>
struct Collection : public CollectionBase<ListType, T, Transfer>,
public ExtraBase
{
protected:
using self_type = Collection;
using super_type = CollectionBase<ListType, T, Transfer>;
public:
using list_ops = typename super_type::list_ops;
protected:
using ElTransfer = typename element_transfer<Transfer>::type;
// decompose pair in case of map
template<typename G>
struct get_types
{
// map to harmless type if no key applicable
using key_type = bool;
using mapped_type = G;
using cpp_type = typename traits::cpptype<G, ElTransfer>::type;
};
template<typename T1, typename T2>
struct get_types<std::pair<T1, T2>>
{
using key_type = T1;
using mapped_type = T2;
using cpp_type = std::pair<typename traits::cpptype<T1, ElTransfer>::type,
typename traits::cpptype<T2, ElTransfer>::type>;
};
using KeyCType = typename get_types<T>::key_type;
using ElCType = typename get_types<T>::mapped_type;
using KeyCppType = typename traits::cpptype<KeyCType, ElTransfer>::type;
using ElCppType = typename traits::cpptype<ElCType, ElTransfer>::type;
using ValueCppType = typename get_types<T>::cpp_type;
// sanity/consistency checks
// expect normalized template types
static_assert(
std::is_same<ListType, typename std::decay<ListType>::type>::value, "");
static_assert(
std::is_same<KeyCppType, typename std::decay<KeyCppType>::type>::value,
"");
static_assert(
std::is_same<ElCppType, typename std::decay<ElCppType>::type>::value, "");
// internal wrap of list-case
Collection(ListType *l, bool own)
{
this->data_ = ((!own && l) ? list_ops::ref(l) : l);
}
// internal wrap of span case
// (also avoid conflict with a public constructor below)
template<typename Enable = void>
Collection(ElCType *d, size_t s, std::nullptr_t) : super_type(d, s)
{}
template<typename T1, typename T2, typename LVR>
std::pair<gpointer, gpointer> unwrap_item(std::pair<T1, T2> &el, LVR)
{
// NOTE if compiler error occurs here, perhaps std::move() input
auto item1 = unwrap(cast_ref(el.first, LVR()), ElTransfer());
static_assert(std::is_pointer<decltype(item1)>::value, "expected pointer");
auto item2 = unwrap(cast_ref(el.second, LVR()), ElTransfer());
static_assert(std::is_pointer<decltype(item2)>::value, "expected pointer");
return {(gpointer)item1, (gpointer)item2};
};
template<typename T1, typename LVR>
typename list_ops::value_type unwrap_item(T1 &el, LVR)
{
// NOTE if compiler error occurs here, perhaps std::move() input
auto item = unwrap(cast_ref(el, LVR()), ElTransfer());
// only force pointers or same size to same size
using item_type = decltype(item);
static_assert(
std::is_pointer<item_type>::value ==
std::is_pointer<typename list_ops::value_type>::value &&
sizeof(item_type) == sizeof(typename list_ops::value_type),
"expected pointer or matched size");
return (typename list_ops::value_type)item;
}
template<typename Iterator, typename LVR>
void create_from(Iterator first, Iterator last, LVR)
{
// empty container is not null, so always init/create
// NOTE should also add leading/initial zero if needed
Notify::_updated(this, true);
auto s = this->_create();
while (first != last) {
auto &el = *first;
auto item = unwrap_item(el, LVR());
s = this->_push(s, item);
++first;
}
// possible (size) check might be done here
this->_finish();
}
// array as surrogate generic container
template<typename InputT>
void create_from_array(InputT *d, size_t s, std::false_type)
{
create_from(d, d + s, std::true_type());
}
// array shortcut case; from plain array to matching span
template<typename InputT>
void create_from_array(InputT *d, size_t s, std::true_type)
{
// should be around as this should be Span case
// add cast to avoid const issues etc
using v_type = typename list_ops::value_type;
static_assert(sizeof(v_type) == sizeof(InputT), "");
Notify::_updated(this, true);
this->_create_from((v_type *)d, s);
}
template<typename Out, typename Enable = void>
struct get_value_type
{
using type = typename Out::container_type::value_type;
};
template<typename Tp>
struct get_value_type<Tp *>
{
using type = typename std::decay<Tp>::type;
};
template<typename OutIterator, typename T1, typename T2>
void wrap_item_add(std::pair<T1, T2> &el, OutIterator &out)
{
using DestType = typename get_value_type<OutIterator>::type;
// expected to be a pair
using DestType1 = typename std::tuple_element<0, DestType>::type;
using DestType2 = typename std::tuple_element<1, DestType>::type;
static_assert(!(std::is_same<Transfer, transfer_full_t>::value &&
(traits::is_reftype<DestType1>::value ||
traits::is_reftype<DestType2>::value)),
"full transfer to reftype");
auto item1 = wrap((KeyCType)el.first, ElTransfer());
auto item2 = wrap((ElCType)el.second, ElTransfer());
*out = std::make_pair(std::move(item1), std::move(item2));
};
template<typename OutIterator, typename T1>
void wrap_item_add(T1 &el, OutIterator &out)
{
// validation check; no transfer full to a reftype
using DestType = typename get_value_type<OutIterator>::type;
// sanity check that we probably got the right type
static_assert(std::is_convertible<ElCppType, DestType>::value, "");
static_assert(!(std::is_same<Transfer, transfer_full_t>::value &&
traits::is_reftype<DestType>::value),
"full transfer to reftype");
using item_type = typename std::decay<decltype(el)>::type;
// would also lead to cast failure below, but make it more expressive
static_assert(
std::is_pointer<item_type>::value == std::is_pointer<ElCType>::value &&
sizeof(item_type) == sizeof(ElCType),
"expected pointer or matched size");
// could have const char** or so, so let's (const) cast the hard way
*out = wrap((ElCType)el, ElTransfer());
}
public:
// some common type blurbs
using value_type = typename super_type::value_type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = value_type &;
using const_reference = const value_type &;
static GType get_type_() { return list_ops::get_type_(); }
// used by code generation (for non-plain array case)
// but could be of general use
template<typename OutIterator>
void move_to(OutIterator out) &&
{
// NOTE assumption;
// out creates as it goes (e.g. back_inserter)
// or there is enough output space
typename list_ops::value_type item;
auto it = this->_iterator();
while (it.next(item)) {
wrap_item_add(item, out);
++out;
}
// clear out elements without destroying them if needed
// (if we pulled their ownership with full transfer)
constexpr bool steal = std::is_same<ElTransfer, transfer_full_t>::value;
// provide for equally cleared state in either case
Notify::_updated(this, false);
if (steal && this->data_) {
this->_steal();
} else if (this->data_) {
this->~self_type();
this->data_ = nullptr;
}
}
public:
// group a few helpers
struct _detail
{
using DataType = decltype(std::declval<super_type>().gobj_());
// check if it is possible to create a container
// a none variant does not assume ownership
// so it is not in a position to create a collection (with no other owner)
// however, as usual, the ref types can always own
// also, allow tweaking this restriction (e.g. for holder subclass below)
static constexpr bool constructible =
!std::is_same<Transfer, transfer_none_t>::value || list_ops::refcnt ||
Notify::construct_none;
static constexpr bool is_span = !std::is_class<typename std::decay<
decltype(*std::declval<super_type>().gobj_())>::type>::value;
template<bool B, typename Enable = void>
struct span_size_impl : public std::integral_constant<int, 0>
{};
template<typename Enable>
struct span_size_impl<true, Enable>
: public std::integral_constant<int,
super_type::SPAN_SIZE >= 0 ? super_type::SPAN_SIZE : 0>
{};
static constexpr int span_size = span_size_impl<is_span>::value;
static constexpr bool is_fixed_span = is_span && span_size > 0;
// check if this is Span<> collection of plain V elements
// in case none transfer, we can also copy around any typical element
// (the deref of data_ is so we only end up matching a Span<> case)
template<typename V>
using is_plain = typename std::conditional<
is_span &&
(traits::is_plain<typename std::decay<V>::type>::value ||
std::is_same<Transfer, transfer_none_t>::value) &&
std::is_same<typename list_ops::value_type,
typename std::decay<V>::type>::value,
std::true_type, std::false_type>::type;
// check Cpp type compatibility
// this could be triggered on non-complete V, in which case
// std::is_convertible is undefined, so guard that approach
template<typename V, bool Complete = false>
struct is_acceptable_helper : public std::integral_constant<bool, false>
{};
template<typename V>
struct is_acceptable_helper<V, true>
: public std::is_convertible<V, ValueCppType>
{};
template<typename V>
using is_acceptable = typename is_acceptable_helper<V,
traits::is_type_complete<V>::value>::type;
// likewise, deal with const variations on e.g. char**
// (as char* can be converted to const char*)
template<typename CType>
using is_compatible =
typename std::conditional<std::is_convertible<ElCType, CType>::value,
std::true_type, std::false_type>::type;
};
Collection(std::nullptr_t = nullptr) {}
// a non-owning accepts from all other types
// (either the other owns or there is another non-owner already)
template<typename OtherTransfer,
typename Enable = typename std::enable_if<
!std::is_same<void, OtherTransfer>::value &&
std::is_same<Transfer, transfer_none_t>::value>::type>
Collection(const Collection<ListType, T, OtherTransfer> &cother)
{
// never mind any const, as usual
auto &other = const_cast<Collection<ListType, T, OtherTransfer> &>(cother);
this->data_ = other.gobj_() ? list_ops::ref(other.gobj_()) : nullptr;
}
// construct from reasonably matching container
// NOTE may need r-value input for non-movable (content) types
template<typename Container,
typename Check = typename std::enable_if<
_detail::template is_acceptable<
typename std::decay<Container>::type::value_type>::value &&
_detail::constructible>::type,
typename Enable =
typename detail::disable_if_same_or_derived<Collection, Container>>
Collection(Container &&c)
{
create_from(
std::begin(c), std::end(c), std::is_lvalue_reference<Container>());
}
// construct from initializer list
template<typename InitElement,
typename Check = typename std::enable_if<
_detail::template is_acceptable<InitElement>::value &&
_detail::constructible>::type,
typename Enable =
typename detail::disable_if_same_or_derived<Collection, Container>>
Collection(std::initializer_list<InitElement> c)
{
// initializer_list iterator is a const iterator, so no moving from
create_from(std::begin(c), std::end(c), std::true_type());
}
// construct from array
// (these arguments could come from a vector, or span, or ...)
// NOTE no evident way to mark as r-value/movable-from,
// so it will just have to do and work out
template<typename InputT,
typename Check = typename std::enable_if<
_detail::template is_acceptable<InputT>::value &&
_detail::constructible>::type>
Collection(InputT *d, size_t s)
{
create_from_array(d, s, typename _detail::template is_plain<InputT>());
}
// construct from fixed-size array if this is a fixed-size Span<> case
// NOTE other notes as above apply
template<typename InputT,
typename Check = typename std::enable_if<
_detail::template is_acceptable<InputT>::value &&
_detail::constructible>::type,
typename Enable = typename std::enable_if<
!std::is_same<InputT, void>::value && _detail::is_fixed_span>::type>
Collection(InputT (&d)[_detail::span_size])
{
create_from_array(d, super_type::SPAN_SIZE,
typename _detail::template is_plain<InputT>());
}
// Avoid std::allocator type; check for const_iterator member.
// Disable conversion to an std::initializer_list (also has const_iterator).
// (in C++11 std::vector<T>::operator= is overloaded to take either a
// std::vector<T> or an std::initializer_list<T>).
// move to container
// (clear/empty this one)
template<typename Container,
typename Check = typename std::enable_if<
_detail::template is_acceptable<
typename std::decay<Container>::type::value_type>::value &&
sizeof(typename std::decay<Container>::type::const_iterator) &&
!trait::is_initializer_list<
typename std::remove_reference<Container>::type>::value>::type>
operator Container() &&
{
Container result;
std::move(*this).move_to(std::inserter(result, result.end()));
return result;
}
// generic method(s)
GI_DISABLE_METHOD_NONE
value_type &front() { return *this->begin(); }
const value_type &front() const { return *this->begin(); }
#define GI_ENABLE_METHOD_ARRAY_NOT_NONE \
template<typename Enable = void, \
typename Check = typename std::enable_if< \
std::is_void<Enable>::value && \
std::is_pointer<typename super_type::const_iterator>::value && \
!std::is_same<Transfer, transfer_none_t>::value>::type>
#define GI_ENABLE_METHOD_ARRAY \
template<typename Enable = void, \
typename Check = typename std::enable_if< \
std::is_void<Enable>::value && \
std::is_pointer<typename super_type::const_iterator>::value>::type>
GI_ENABLE_METHOD_ARRAY_NOT_NONE
value_type &back() { return *(this->end() - 1); }
GI_ENABLE_METHOD_ARRAY
const value_type &back() const { return *(this->end() - 1); }
GI_ENABLE_METHOD_ARRAY_NOT_NONE
value_type *data() { return this->begin(); }
GI_ENABLE_METHOD_ARRAY
const value_type *data() const { return this->begin(); }
GI_ENABLE_METHOD_ARRAY_NOT_NONE
value_type &operator[](size_type pos) { return *(this->begin() + pos); }
GI_ENABLE_METHOD_ARRAY
const value_type &operator[](size_type pos) const
{
return *(this->begin() + pos);
}
GI_ENABLE_METHOD_ARRAY_NOT_NONE
value_type &at(size_type pos)
{
if (pos >= this->size())
try_throw(std::out_of_range("Collection::at"));
return *(this->begin() + pos);
}
GI_ENABLE_METHOD_ARRAY
const value_type &at(size_type pos) const
{
if (pos >= this->size())
try_throw(std::out_of_range("Collection::at"));
return *(this->begin() + pos);
}
GI_ENABLE_METHOD_ARRAY
void resize(size_type s)
{
auto size = this->size();
if (s < size) {
this->erase(this->begin() + s, this->end());
} else if (s > size) {
auto cnt = s - size;
while (cnt) {
this->push_back({});
--cnt;
}
}
}
GI_DISABLE_METHOD_NONE
void swap(Collection &other)
{
std::swap((super_type &)*this, (super_type &)other);
}
#undef GI_ENABLE_METHOD_ARRAY
#undef GI_ENABLE_METHOD_ARRAY_NOT_NONE
// in unwrap below;
// refcnt based cases support all sorts of transfer,
// otherwise transfer must match the Transfer encoded in type
// r-value case; data can be snatched from this instance
template<typename ReqTransfer,
typename std::enable_if<std::is_same<Transfer, ReqTransfer>::value ||
list_ops::refcnt>::type * = nullptr>
typename _detail::DataType _unwrap(const ReqTransfer &t) &&
{
auto l = this->data_;
// be nice to subtype below
// avoid yanking away possibly owned value
if (t.value != transfer_none.value)
this->data_ = nullptr;
return l;
}
// l-value case; no snatching, so only transfer none (if not refcnt)
template<typename ReqTransfer,
typename std::enable_if<
list_ops::refcnt ||
(std::is_same<Transfer, ReqTransfer>::value &&
std::is_same<ReqTransfer, transfer_none_t>::value)>::type * =
nullptr>
typename _detail::DataType _unwrap(const ReqTransfer &t) &
{
if (list_ops::refcnt)
return ((t.value != transfer_none.value) && this->data_)
? list_ops::ref(this->data_)
: this->data_;
// must be none transfer in this case
return this->data_;
}
template<typename CppType, typename ReqTransfer,
typename std::enable_if<std::is_same<Transfer, ReqTransfer>::value &&
!_detail::is_span>::type * = nullptr>
static CppType _wrap(const ListType *obj, const ReqTransfer &t)
{
static_assert(sizeof(CppType) == sizeof(self_type), "invalid wrap");
static_assert(std::is_base_of<self_type, CppType>::value, "invalid wrap");
self_type w(const_cast<ListType *>(obj), t.value);
return std::move(*static_cast<CppType *>(&w));
}
// special case; wrap of 2 arguments (ptr and size)
// (could be combined in a single span,
// but it would have to be unpacked anyway)
template<typename CppType, typename CType, typename ReqTransfer,
typename std::enable_if<
std::is_same<Transfer, ReqTransfer>::value && _detail::is_span &&
_detail::template is_compatible<CType>::value>::type * = nullptr>
static CppType _wrap(CType *obj, int s, const ReqTransfer &)
{
static_assert(sizeof(CppType) == sizeof(self_type), "invalid wrap");
static_assert(std::is_base_of<self_type, CppType>::value, "invalid wrap");
// select internal protected constructor
self_type w(const_cast<ElCType *>(obj), s, nullptr);
return std::move(*static_cast<CppType *>(&w));
}
// likewise, ZT
template<typename CppType, typename CType, typename ReqTransfer,
typename std::enable_if<
std::is_same<Transfer, ReqTransfer>::value && _detail::is_span &&
_detail::span_size == SpanType::ZT>::type * = nullptr>
static CppType _wrap(CType *obj, const ReqTransfer &)
{
return _wrap<CppType>(obj, -1, ReqTransfer());
}
template<typename CppType,
typename std::enable_if<!std::is_void<CppType>::value &&
list_ops::refcnt>::type * = nullptr>
static CppType _get_value(const GValue *v)
{
auto wv = std::is_same<Transfer, transfer_none_t>::value()
? g_value_get_boxed(v)
: g_value_dup_boxed(v);
return _wrap<CppType>((ListType *)(wv), Transfer());
}
template<typename CppType,
typename std::enable_if<!std::is_void<CppType>::value &&
!list_ops::refcnt>::type * = nullptr>
static CppType _get_value(const GValue *v)
{
// tracked as raw gpointer
auto wv = g_value_get_pointer(v);
return _wrap<CppType>((ElCType *)(wv), Transfer());
}
void _set_value(GValue *v)
{
if (list_ops::refcnt) {
g_value_set_boxed(v, this->gobj_());
} else {
g_value_set_pointer(v, this->gobj_());
}
}
};
// add additional owner tracking data
// (see below for reason of simple/silly separate type)
struct OwnerData : public Container
{
bool own_ = false;
};
template<typename... Args>
struct NotifierThunk;
// should only end up used with transfer_none
template<typename ListType, typename T, typename Transfer = transfer_none_t>
class CollectionHolder : public Collection<ListType, T, Transfer,
NotifierThunk<ListType, T, Transfer>, OwnerData>
{
// ownership tracked by OwnerData
// as notified by helper which needs access
friend struct NotifierThunk<ListType, T, Transfer>;
// to call here
void _updated(bool create)
{
if (create) {
this->own_ = true;
} else {
this->~CollectionHolder();
}
}
public:
// NOTE OwnerData is inserted as a lower base class to ensure it is
// initialized soon enough (before the using'ed constructor will notify
// of creation)
using super_type = Collection<ListType, T, Transfer,
NotifierThunk<ListType, T, Transfer>, OwnerData>;
using super_type::super_type;
// only allowed (limited) move
// (even if some parents might allow more)
CollectionHolder(CollectionHolder &&other) : super_type(std::move(other))
{
this->own_ = other.own_;
other.own_ = false;
}
CollectionHolder &operator=(CollectionHolder &&other)
{
if (this != &other) {
this->~CollectionHolder();
(super_type &)(*this) = std::move(other);
this->own_ = other.own_;
other.own_ = false;
}
return *this;
}
~CollectionHolder()
{
// only need to act if super type does not handle (owns) anyway
// only free/clear list, items not owned
if (std::is_same<Transfer, transfer_none_t>::value && this->own_ &&
this->data_) {
this->_steal();
this->own_ = false;
}
}
};
#undef GI_DISABLE_METHOD_NONE
template<typename... Args>
struct NotifierThunk
{
static constexpr bool construct_none = true;
static void _updated(Container *c, bool create)
{
using CT = CollectionHolder<Args...>;
return static_cast<CT *>(c)->_updated(create);
}
};
struct glib_deleter
{
template<typename T>
void operator()(T *p)
{
g_free(p);
}
};
template<typename T>
using unique_ptr = std::unique_ptr<T, glib_deleter>;
template<typename ListType, typename ElType, typename Transfer,
typename Enable = void>
struct ListAcceptorImpl
{
using type = Collection<ListType, ElType, Transfer>;
};
template<typename ListType, typename ElType, typename Transfer>
struct ListAcceptorImpl<ListType, ElType, Transfer,
typename std::enable_if<
(std::is_same<ListType, ::GList>::value ||
std::is_same<ListType, ::GSList>::value) &&
std::is_same<Transfer, transfer_none_t>::value>::type>
{
using type = CollectionHolder<ListType, ElType, Transfer>;
};
} // namespace detail
using detail::unwrap;
using ZTSpan = detail::Span<detail::SpanType::ZT>;
using DSpan = detail::Span<detail::SpanType::DYNAMIC>;
template<int S>
using FSpan = detail::Span<S>;
// only the full_transfer version is really safe for general use
// but let's export it all
using detail::Collection;
// type to use in (input) parameter declaration
// use basic type for ref-cases, otherwise need Holder subtype
// NOTE add a workaround hack;
// use full transfer for a none transfer HashTable parameter
// (as annotation there might be bogus; looking at add gst_uri_set_query_table)
// (and if the annotiation is not bogus, the full should work out ok-ish)
// NOTE list_ops is used directly hereto avoid instantiation of Collection
// (as that might otherwise occur during declaration)
template<typename ListType, typename T, typename Transfer>
using CollectionParameter = typename std::conditional<
std::is_same<ListType, ::GHashTable>::value &&
std::is_same<Transfer, transfer_none_t>::value,
detail::Collection<ListType, T, transfer_full_t>,
typename std::conditional<detail::list_ops<ListType, T, Transfer>::refcnt,
detail::Collection<ListType, T, Transfer>,
detail::CollectionHolder<ListType, T, Transfer>>::type>::type;
namespace traits
{
// (many to one) map from Cpp to C type (so no other way around)
template<typename ListType, typename El, typename Transfer>
struct ctype<detail::Collection<ListType, El, Transfer>>
{
using type =
typename detail::Collection<ListType, El, Transfer>::_detail::DataType;
};
template<typename ListType, typename El, typename Transfer>
struct ctype<const detail::Collection<ListType, El, Transfer>>
{
using type = const typename detail::Collection<ListType, El,
Transfer>::_detail::DataType;
};
} // namespace traits
} // namespace gi
#endif // GI_CONTAINER_HPP