Files
tdesktop/cmake/external/glib/cppgir/gi/string.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
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
init
2026-02-16 15:50:16 +03:00

875 lines
21 KiB
C++

#ifndef GI_STRING_HPP
#define GI_STRING_HPP
#include "base.hpp"
#ifdef __has_builtin
#define GI_HAS_BUILTIN(x) __has_builtin(x)
#else
#define GI_HAS_BUILTIN(x) 0
#endif
GI_MODULE_EXPORT
namespace gi
{
namespace convert
{
// generic template; specialize as needed where appropriate
template<typename From, typename To, class Enable = void>
struct converter
{
// fail in explanatory way if we end up here
static To convert(const From &)
{
static_assert(!std::is_void<Enable>::value, "unknown type conversion");
return To();
}
};
// implementation should provide some types
template<typename From, typename To, class Enable = void>
struct converter_base : public std::true_type
{
typedef From from_type;
typedef To to_type;
};
// conversion check for complete type
template<typename From, typename To, typename Enable = void>
struct is_convertible_impl : public std::false_type
{};
template<typename From, typename To>
struct is_convertible_impl<From, To,
typename std::enable_if<std::is_base_of<To, From>::value>::type>
: public std::false_type
{};
template<typename From, typename To>
struct is_convertible_impl<From, To,
typename std::enable_if<!std::is_base_of<To, From>::value &&
std::is_pointer<typename converter<From,
To>::from_type *>::value>::type>
: public std::true_type
{};
template<typename From, typename To, bool complete>
struct is_convertible_pre
{
using type = std::false_type;
};
template<typename From, typename To>
struct is_convertible_pre<From, To, true>
{
using type = is_convertible_impl<From, To>;
};
// check whether conversion possible
// reject incomplete forward declared types
template<typename From, typename To, typename Enable = void>
struct is_convertible : public is_convertible_pre<From, To,
gi::traits::is_type_complete<To>::value>::type
{};
} // namespace convert
namespace detail
{
// tag
struct String
{};
template<typename Transfer>
struct StringFuncs
{
static void _deleter(char *&p)
{
if (Transfer().value)
g_free(p);
p = nullptr;
}
static void _copy(const char *p)
{
return Transfer().value ? g_strdup(p) : p;
}
};
template<typename Transfer>
class cstr : public String
{
using self_type = cstr;
protected:
using _member_type =
typename std::conditional<std::is_same<Transfer, transfer_full_t>::value,
char, const char>::type;
_member_type *data_ = nullptr;
void clear()
{
if (Transfer().value && data_)
g_free((char *)data_);
data_ = nullptr;
}
public:
using traits_type = std::char_traits<char>;
using value_type = char;
using pointer = char *;
using const_pointer = const char *;
using reference = char &;
using const_reference = const char &;
using const_iterator = const char *;
using iterator = const_iterator;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using reverse_iterator = const_reverse_iterator;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
using view_type = cstr<transfer_none_t>;
static constexpr bool is_view_type =
!std::is_same<Transfer, transfer_full_t>::value;
static constexpr size_type npos = static_cast<size_type>(-1);
constexpr cstr() noexcept : data_(nullptr) {}
constexpr cstr(std::nullptr_t) noexcept : data_(nullptr) {}
// const usually means none, so only on view type
// also, no arbitrary size is accepted here
template<typename Enable = void,
typename std::enable_if<std::is_same<Enable, void>::value &&
is_view_type>::type * = nullptr>
constexpr cstr(const char *data) : data_(data)
{}
// a single pointer, optionally specify ownership
// behave like string by default, assume no ownership of incoming pointer
template<typename LTransfer = transfer_none_t,
typename std::enable_if<
(std::is_same<LTransfer, transfer_full_t>::value ||
std::is_same<LTransfer, transfer_none_t>::value) &&
!is_view_type>::type * = nullptr>
cstr(char *data, const LTransfer &t)
: data_((t.value == transfer_full.value) || !data ? data : g_strdup(data))
{}
// pointer along with size
// always behave like string and make a copy
template<typename Enable = void,
typename std::enable_if<std::is_same<Enable, void>::value &&
!is_view_type>::type * = nullptr>
cstr(const char *data, size_t len = npos)
: data_(!data ? nullptr
: (len == npos ? g_strdup(data) : g_strndup(data, len)))
{}
// construct from any transfer variant
template<typename OTransfer>
cstr(const cstr<OTransfer> &s) : cstr(s.data())
{}
// accept from string, but NOT string_view as that may not be null-terminated
template<typename Allocator>
cstr(const std::basic_string<char, std::char_traits<char>, Allocator>
&s) noexcept
: cstr(s.data())
{}
#if __cplusplus >= 201703L
// some optional variants of above
constexpr cstr(std::nullopt_t) noexcept : data_(nullptr) {}
template<typename Allocator>
cstr(const std::optional<
std::basic_string<char, std::char_traits<char>, Allocator>> &s) noexcept
: cstr(s ? s.value().data() : nullptr)
{}
#endif
// hook extensible conversion
// (avoid instantiation and confusion with self and base types)
template<typename From,
typename NoBase = typename std::enable_if<
!std::is_base_of<self_type, From>::value>::type,
typename Enable = typename std::enable_if<
convert::is_convertible<From, self_type>::value>::type>
cstr(const From &f) : cstr(convert::converter<From, self_type>::convert(f))
{}
// custom
constexpr const_pointer gobj_() const { return data_; }
explicit constexpr operator bool() const { return data_; }
_member_type *release_()
{
auto tmp = this->data_;
this->data_ = nullptr;
return tmp;
}
// destruct / copy / assign
~cstr() { clear(); }
cstr(const cstr &other) { *this = other; }
cstr(cstr &&other) { *this = std::move(other); }
cstr &operator=(const cstr &other)
{
if (this != &other) {
clear();
data_ = Transfer().value ? g_strdup(other.data_) : other.data_;
}
return *this;
}
cstr &operator=(cstr &&other)
{
if (this != &other) {
clear();
data_ = other.data_;
other.data_ = nullptr;
}
return *this;
}
// deduced conversion to string(_view)
template<typename Destination,
typename Check = typename std::enable_if<
convert::is_convertible<self_type, Destination>::value>::type>
operator Destination() const
{
return convert::converter<self_type, Destination>::convert(*this);
}
#if __cplusplus >= 201703L
// unfortunately, conversion to std::optional picks Destination = std::string
// (which can obviously not be excluded above, or as specialization)
std::optional<std::string> opt_()
{
using To = std::optional<std::string>;
return c_str() ? To({c_str(), size()}) : To(std::nullopt);
}
#endif
// usual string(view) stuff
// iterators
constexpr const_iterator begin() const noexcept { return data_; }
constexpr const_iterator end() const noexcept
{
return data_ ? data_ + size() : nullptr;
}
constexpr const_iterator cbegin() const noexcept { return begin(); }
constexpr const_iterator cend() const noexcept { return end(); }
const_reverse_iterator rbegin() const noexcept
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend() const noexcept
{
return const_reverse_iterator(begin());
}
const_reverse_iterator crbegin() const noexcept { return rbegin(); }
const_reverse_iterator crend() const noexcept { return rend(); }
// capacity
constexpr size_type size() const noexcept
{
#if GI_HAS_BUILTIN(__builtin_strlen) || \
(defined(__GNUC__) && !defined(__clang__))
return __builtin_strlen(data_);
#else
return data_ ? strlen(data_) : 0;
#endif
}
constexpr size_type length() const noexcept { return size(); }
constexpr size_type max_size() const noexcept
{
return std::numeric_limits<size_type>::max();
}
constexpr bool empty() const noexcept { return !data_ || *data_ == 0; }
// access
constexpr const_reference operator[](size_type i) const { return data_[i]; }
constexpr const_reference at(size_type i) const
{
return i < size() ? data_[i]
: (try_throw(std::out_of_range("cstr::at")), data_[i]);
}
constexpr const_reference front() const { return data_[0]; }
constexpr const_reference back() const { return data_[size() - 1]; }
constexpr const_pointer data() const noexcept { return data_; }
constexpr const_pointer c_str() const noexcept { return data_; }
// modifiers
// view only
template<typename Enable = void,
typename std::enable_if<std::is_same<Enable, void>::value &&
is_view_type>::type * = nullptr>
constexpr void remove_prefix(size_type n)
{
data_ += n;
}
constexpr void swap(self_type &s) noexcept
{
std::swap(this->data_, s.data_);
}
// operations
size_type copy(char *buf, size_type n, size_type pos = 0) const
{
auto s = size();
if (pos > s)
try_throw(std::out_of_range("cstr::copy"));
size_type rlen = (std::min)(s - pos, n);
if (rlen > 0) {
const char *start = data_ + pos;
traits_type::copy(buf, start, rlen);
}
return rlen;
}
int compare(view_type x) const noexcept
{
return g_strcmp0(data_, x.c_str());
}
int compare(const char *s) const { return compare(view_type(s)); }
// find
size_type find(view_type n, size_type pos = 0) const noexcept
{
auto s = size();
auto os = n.size();
if (os > s || pos > s - os)
return npos;
if (!os)
return pos <= s ? pos : npos;
auto loc = strstr(data_ + pos, n.data());
return loc ? loc - data_ : npos;
}
size_type find(char c, size_type pos = 0) const noexcept
{
if (pos >= size())
return npos;
auto loc = strchr(data_ + pos, c);
return loc ? loc - data_ : npos;
}
size_type find(const char *s, size_type pos = 0) const
{
return find(view_type(s), pos);
}
size_type rfind(view_type n, size_type pos = npos) const noexcept
{
auto s = size();
if (!s)
return npos;
auto os = n.size();
if (!os)
return pos == npos ? s : pos;
auto loc = g_strrstr_len(c_str(), pos, n.c_str());
return loc ? loc - data_ : npos;
}
size_type rfind(char c, size_type pos = npos) const noexcept
{
// not quite efficient, but anyways
char str[] = {c, 0};
return rfind(str, pos);
}
size_type rfind(const char *s, size_type pos = npos) const
{
return rfind(view_type(s), pos);
}
// find_first_of variants
// only provide those if std helps us out
#if __cplusplus >= 201703L
size_type find_first_of(view_type s, size_type pos = 0) const noexcept
{
return std::string_view(data_).find_first_of(s.data(), pos);
}
size_type find_first_of(char c, size_type pos = 0) const noexcept
{
return find(c, pos);
}
size_type find_first_of(const char *s, size_type pos = 0) const
{
return find_first_of(view_type(s), pos);
}
size_type find_last_of(view_type s, size_type pos = npos) const noexcept
{
return std::string_view(data_).find_last_of(s.data(), pos);
}
size_type find_last_of(char c, size_type pos = npos) const noexcept
{
return rfind(c, pos);
}
size_type find_last_of(const char *s, size_type pos = npos) const
{
return find_last_of(view_type(s), pos);
}
size_type find_first_not_of(view_type s, size_type pos = 0) const noexcept
{
return std::string_view(data_).find_first_not_of(s.data(), pos);
}
size_type find_first_not_of(char c, size_type pos = 0) const noexcept
{
return std::string_view(data_).find_first_not_of(c, pos);
}
size_type find_first_not_of(const char *s, size_type pos = 0) const
{
return find_first_not_of(view_type(s), pos);
}
size_type find_last_not_of(view_type s, size_type pos = npos) const noexcept
{
return std::string_view(data_).find_last_not_of(s.data(), pos);
}
size_type find_last_not_of(char c, size_type pos = npos) const noexcept
{
return std::string_view(data_).find_last_not_of(c, pos);
}
size_type find_last_not_of(const char *s, size_type pos = npos) const
{
return find_last_not_of(view_type(s), pos);
}
#endif
};
using _string_view = cstr<transfer_none_t>;
inline bool
operator==(_string_view x, _string_view y) noexcept
{
return x.compare(y) == 0;
}
inline bool
operator!=(_string_view x, _string_view y) noexcept
{
return !(x == y);
}
inline bool
operator<(_string_view x, _string_view y) noexcept
{
return x.compare(y) < 0;
}
inline bool
operator>(_string_view x, _string_view y) noexcept
{
return y < x;
}
inline bool
operator<=(_string_view x, _string_view y) noexcept
{
return !(y < x);
}
inline bool
operator>=(_string_view x, _string_view y) noexcept
{
return !(x < y);
}
#ifndef GI_NO_STRING_IOS
inline std::ostream &
operator<<(std::ostream &o, _string_view sv)
{
// backwards compatibility; behave similar to empty string
return o << (sv ? sv.c_str() : "");
}
#endif
// purpose of silly Delay is to avoid gcc premature instantiation of converter
// (in the hook constructor of base class with cstring as From ??)
template<typename Delay = void>
class cstring_d : public cstr<transfer_full_t>
{
using self_type = cstring_d;
using super_type = cstr<transfer_full_t>;
public:
using super_type::super_type;
// if all other construction fails, try to pass through string
template<typename... Args,
typename NoConvert = typename std::enable_if<
!std::is_constructible<super_type, Args...>::value>::type,
typename Enable = typename std::enable_if<
std::is_constructible<std::string, Args...>::value>::type>
cstring_d(Args &&...args)
: super_type(std::string(std::forward<Args>(args)...))
{}
constexpr pointer gobj_() { return data_; }
// re-use string
template<typename... Args>
self_type &assign(Args &&...t)
{
return *this = std::string().assign(std::forward<Args>(t)...);
}
// access
using super_type::at;
constexpr reference at(size_type i)
{
return i < size() ? data_[i]
: (try_throw(std::out_of_range("cstr::at")), data_[i]);
}
using super_type::front;
constexpr reference front() { return data_[0]; }
using super_type::back;
constexpr reference back() { return data_[size() - 1]; }
using super_type::data;
constexpr pointer data() noexcept { return data_; }
// iterators
using super_type::begin;
constexpr iterator begin() noexcept { return data_; }
using super_type::end;
constexpr iterator end() noexcept { return data_ ? data_ + size() : nullptr; }
using super_type::rbegin;
reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
using super_type::rend;
reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
// operations
using super_type::clear;
void push_back(char c)
{
char str[] = {c, 0};
self_type n{
g_strconcat(c_str() ? c_str() : "", str, (char *)NULL), transfer_full};
swap(n);
}
void pop_back()
{
auto s = size();
if (s)
at(s - 1) = 0;
}
self_type &append(const char *str)
{
self_type n{
g_strconcat(c_str() ? c_str() : "", str, (char *)NULL), transfer_full};
swap(n);
return *this;
}
template<typename Transfer>
self_type &append(const cstr<Transfer> &str)
{
return append(str.data());
}
// otherwise delegate
template<typename... Args>
self_type &append(Args &&...args)
{
std::string s;
s.append(std::forward<Args>(args)...);
// make sure to select the desired variant
return append((const char *)s.c_str());
}
// likewise for +=
self_type &operator+=(char c)
{
push_back(c);
return *this;
}
template<typename T>
self_type &operator+=(const T &o)
{
return append(o);
}
self_type substr(size_type pos = 0, size_type n = npos) const
{
auto l = size();
return (pos > l)
? (try_throw(std::out_of_range("cstr::substr")), self_type())
: self_type(data_ + pos, std::min(n, l - pos));
}
// indeed some are lacking/skipped
// only minimal compatibility
// custom; align with other cases
self_type copy_() { return substr(0); }
};
using cstring = cstring_d<>;
// likewise; (delayed) view type
template<typename Delay = void>
class cstring_v_d : public cstr<transfer_none_t>
{
using self_type = cstring_v_d;
using super_type = cstr<transfer_none_t>;
public:
using super_type::super_type;
// primary reason for subtype
// provide copy/upgrade to owning variant
cstring copy_() { return {g_strdup(this->data()), transfer_full}; }
};
using cstring_v = cstring_v_d<>;
inline cstring
operator+(const _string_view x, const _string_view y) noexcept
{
if (!x)
return {g_strdup(y.c_str()), transfer_full};
if (!y)
return {g_strdup(x.c_str()), transfer_full};
return {g_strconcat(x.c_str(), y.c_str(), (char *)NULL), transfer_full};
}
inline cstring
operator+(const _string_view x, char y) noexcept
{
if (!x)
return {1, y};
char str[] = {y, 0};
return x + str;
}
inline cstring
operator+(char x, const _string_view y) noexcept
{
if (!y)
return {1, x};
char str[] = {x, 0};
return str + y;
}
// add convenient conversions
// local helper traits used below
namespace trait
{
template<typename T, std::size_t SIZE, typename Enable = void>
struct has_data_member : std::false_type
{};
template<typename T, std::size_t SIZE>
struct has_data_member<T, SIZE,
typename std::enable_if<
std::is_pointer<decltype(std::declval<T>().c_str())>::value>::type>
: std::integral_constant<bool, sizeof(*std::declval<T>().c_str()) == SIZE>
{};
template<typename T, typename Enable = void>
struct has_size_member : std::false_type
{};
template<typename T>
struct has_size_member<T, typename std::enable_if<std::is_integral<
decltype(std::declval<T>().size())>::value>::type>
: std::true_type
{};
template<typename T>
struct is_string_type
: public std::integral_constant<bool,
has_data_member<T, 1>::value && has_size_member<T>::value>
{};
} // namespace trait
} // namespace detail
namespace convert
{
template<typename From>
struct converter<From, detail::cstr<transfer_full_t>,
typename std::enable_if<detail::trait::is_string_type<From>::value>::type>
: public converter_base<From, detail::cstr<transfer_full_t>>
{
static detail::cstring convert(const From &v)
{
return {(char *)v.c_str(), v.size()};
}
};
// to a typical string(_view) case
// (avoid conflict with above)
// the traits_type (tries to) restricts this to std::string(_view)
// not doing so might conveniently allow conversion to other types as well.
// however, this would also allow e.g. QByteArray, which comes with an operator+
// in global namespace (also selected by non-ADL lookup),
// which then results in ambiguous overload
// (with the operator+ that is provided above)
template<typename Transfer, typename To>
struct converter<detail::cstr<Transfer>, To,
typename std::enable_if<
!std::is_base_of<detail::String, To>::value &&
!std::is_same<typename To::traits_type, void>::value &&
std::is_constructible<To, const char *, size_t>::value>::type>
: public converter_base<detail::cstr<Transfer>, To>
{
static To convert(const detail::cstr<Transfer> &v)
{
return v.c_str() ? To{(char *)v.c_str(), v.size()} : To();
}
};
#if __cplusplus >= 201703L
// to an std::optional string(_view) case
template<typename Transfer, typename To>
struct converter<detail::cstr<Transfer>, To,
typename std::enable_if<std::is_constructible<To, std::in_place_t,
const char *, size_t>::value>::type>
: public converter_base<detail::cstr<Transfer>, To>
{
static To convert(const detail::cstr<Transfer> &v)
{
abort();
return v.c_str() ? To{std::in_place, (char *)v.c_str(), v.size()} : To();
}
};
#endif
} // namespace convert
using detail::cstring;
using detail::cstring_v;
// sanity check; match C counterpart
static_assert(sizeof(cstring) == sizeof(char *), "");
static_assert(sizeof(cstring_v) == sizeof(char *), "");
namespace traits
{
template<>
struct ctype<const gi::cstring, void>
{
typedef const char *type;
};
template<>
struct ctype<gi::cstring, void>
{
typedef char *type;
};
template<>
struct ctype<const gi::cstring_v, void>
{
typedef const char *type;
};
template<>
struct ctype<gi::cstring_v, void>
{
typedef char *type;
};
template<>
struct cpptype<char *, transfer_full_t>
{
using type = gi::cstring;
};
template<>
struct cpptype<char *, transfer_none_t>
{
using type = gi::cstring_v;
};
} // namespace traits
} // namespace gi
// specialize std::hash for suitable use
GI_MODULE_EXPORT
namespace std
{
template<>
struct hash<gi::cstring>
{
typedef gi::cstring argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const &s) const
{
return s.c_str() ? g_str_hash(s.c_str()) : 0;
}
};
template<>
struct hash<gi::cstring_v>
{
typedef gi::cstring_v argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const &s) const
{
return s.c_str() ? g_str_hash(s.c_str()) : 0;
}
};
} // namespace std
#endif // GI_STRING_HPP