Skip to content

Commit ef0ee72

Browse files
committed
Improve StringLiteral interface
1 parent 52e1bff commit ef0ee72

File tree

8 files changed

+467
-42
lines changed

8 files changed

+467
-42
lines changed

lib/core/include/qx/core/qx-abstracterror.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ class QX_CORE_EXPORT IError
8181
bool operator!=(const IError& other) const = default;
8282
};
8383

84-
template<StringLiteral EName, quint16 ECode>
84+
template<CStringLiteral EName, quint16 ECode>
8585
class AbstractError : protected IError
8686
{
8787
friend class Error;
8888
//-Class Variables----------------------------------------------------------------------------------------------------------
8989
public:
9090
static constexpr quint16 TYPE_CODE = ECode;
91-
static constexpr QLatin1StringView TYPE_NAME{EName.value};
91+
static constexpr QLatin1StringView TYPE_NAME{EName};
9292

9393
private:
9494
static const bool REGISTER;
@@ -124,22 +124,22 @@ friend class Error;
124124
//template<class E>
125125
//concept error_type = requires(E type) {
126126
// // IIFE that ensures E is a specialization of AbstractError
127-
// []<StringLiteral Y, quint16 Z>(AbstractError<Y, Z>&){}(type);
127+
// []<CStringLiteral Y, quint16 Z>(AbstractError<Y, Z>&){}(type);
128128
//};
129129

130130
/* Define error type registrar variable. This must be done out of line to ensure that only
131131
* one instance of the variable exists per-error-type across an entire program. If the variable
132-
* is defined inline, multiple versiosn of it can exist in parallel when linking via shared-libraries,
132+
* is defined inline, multiple versions of it can exist in parallel when linking via shared-libraries,
133133
* if those libraries are used by multiple targets in the same project. This would cause an error type
134134
* to call registerType() multiple times.
135135
*/
136-
template<StringLiteral EName, quint16 ECode>
136+
template<CStringLiteral EName, quint16 ECode>
137137
const bool AbstractError<EName, ECode>::REGISTER = registerType(TYPE_CODE, TYPE_NAME);
138138

139139
/*! @cond */
140140
namespace AbstractErrorPrivate
141141
{
142-
template<Qx::StringLiteral Y, quint16 Z>
142+
template<Qx::CStringLiteral Y, quint16 Z>
143143
void aeDerived(Qx::AbstractError<Y, Z>&);
144144
}
145145
/*! @endcond */

lib/core/include/qx/core/qx-json.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ namespace QxJson
230230
template<typename T>
231231
struct Converter;
232232

233-
template<class Struct, Qx::StringLiteral member>
233+
template<class Struct, Qx::CStringLiteral member>
234234
struct MemberOverrideCoverter;
235235

236236
template<typename SelfType, typename DelayedSelfType>
@@ -261,7 +261,7 @@ concept json_convertible = requires(T& tValue) {
261261
{ Converter<T>::toJson(tValue) } -> qjson_type;
262262
};
263263

264-
template<class K, typename T, Qx::StringLiteral N>
264+
template<class K, typename T, Qx::CStringLiteral N>
265265
concept json_override_convertible = requires(T& tValue) {
266266
{ MemberOverrideCoverter<K, N>::fromJson(tValue, QJsonValue()) } -> std::same_as<Qx::JsonError>;
267267
{ MemberOverrideCoverter<K, N>::toJson(tValue) } -> qjson_type;
@@ -306,16 +306,16 @@ static inline const QString ERR_READ_DATA = u"JSON Error: Could not read JSON da
306306
static inline const QString ERR_WRITE_FILE = u"JSON Error: Could not write JSON file."_s;
307307

308308
//-Structs---------------------------------------------------------------
309-
template<Qx::StringLiteral MemberN, typename MemberT, class Struct>
309+
template<Qx::CStringLiteral MemberN, typename MemberT, class Struct>
310310
struct MemberMetadata
311311
{
312-
constexpr static Qx::StringLiteral M_NAME = MemberN;
312+
constexpr static Qx::CStringLiteral M_NAME = MemberN;
313313
typedef MemberT M_TYPE;
314314
MemberT Struct::* mPtr;
315315
};
316316

317317
//-Functions-------------------------------------------------------------
318-
template <Qx::StringLiteral N, typename T, class S>
318+
template <Qx::CStringLiteral N, typename T, class S>
319319
constexpr MemberMetadata<N, T, S> makeMemberMetadata(T S::*memberPtr)
320320
{
321321
return {memberPtr};
@@ -377,14 +377,14 @@ constexpr auto getMemberMeta()
377377
return QxJson::QxJsonMetaStructOutside<K, K>::memberMetadata();
378378
}
379379

380-
template<class K, typename T, Qx::StringLiteral N>
380+
template<class K, typename T, Qx::CStringLiteral N>
381381
requires QxJson::json_override_convertible<K, T, N>
382382
Qx::JsonError overrideParse(T& value, const QJsonValue& jv)
383383
{
384384
return QxJson::MemberOverrideCoverter<K, N>::fromJson(value, jv);
385385
}
386386

387-
template<class K, typename T, Qx::StringLiteral N>
387+
template<class K, typename T, Qx::CStringLiteral N>
388388
requires QxJson::json_override_convertible<K, T, N>
389389
auto overrideSerialize(const T& value)
390390
{
@@ -465,7 +465,7 @@ struct Converter<T>
465465
([&]{
466466
// Meta
467467
static constexpr auto mName = std::remove_reference<decltype(memberMeta)>::type::M_NAME;
468-
constexpr QLatin1StringView mKey(mName.value);
468+
constexpr QLatin1StringView mKey(mName);
469469
using mType = typename std::remove_reference<decltype(memberMeta)>::type::M_TYPE;
470470
auto& mRef = value.*(memberMeta.mPtr);
471471

@@ -514,7 +514,7 @@ struct Converter<T>
514514
([&]{
515515
// Meta
516516
static constexpr auto mName = std::remove_reference<decltype(memberMeta)>::type::M_NAME;
517-
constexpr QLatin1StringView mKey(mName.value);
517+
constexpr QLatin1StringView mKey(mName);
518518
using mType = typename std::remove_reference<decltype(memberMeta)>::type::M_TYPE;
519519
auto& mRef = value.*(memberMeta.mPtr);
520520

lib/core/src/qx-abstracterror.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,12 @@ QString IError::deriveDetails() const { return QString(); }
191191
//-Class Variables----------------------------------------------------------------------------------------------------------
192192
//Public:
193193
/*!
194-
* @var quint16 AbstractError<StringLiteral EName, quint16 ECode>::TYPE_CODE
194+
* @var quint16 AbstractError<CStringLiteral EName, quint16 ECode>::TYPE_CODE
195195
* The type code of the specific error type. Dictated by @a ECode.
196196
*/
197197

198198
/*!
199-
* @var quint16 AbstractError<StringLiteral EName, quint16 ECode>::TYPE_NAME
199+
* @var quint16 AbstractError<CStringLiteral EName, quint16 ECode>::TYPE_NAME
200200
* The type name of the specific error type. Dictated by @a EName.
201201
*/
202202

lib/utility/doc/res/snippets/qx-stringliteral.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
#include <iostream>
44

55
template<Qx::StringLiteral S>
6-
void printString() { std::cout << "The string is: " << S.value }
6+
void printString()
7+
{
8+
if constexpr(std::same_as<typename decltype(S)::data_t, char>)
9+
std::cout << "The string is: " << S.std_view();
10+
else if constexpr(std::same_as<typename decltype(S)::data_t, wchar_t>)
11+
std::wcout << "The string is: " << S.std_view();
12+
}
713

814
int main()
915
{
10-
printString<"Hello World">();
16+
printString<"Hello">();
17+
printString<L"World">();
1118
return 0;
1219
}
13-
//! [0]
20+
//! [0]

lib/utility/include/qx/utility/qx-concepts.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
// Inter-component Includes
1313
#include "qx/utility/qx-typetraits.h"
1414

15+
namespace _QxPrivate
16+
{
17+
// Helpers
18+
template <template <class...> class Template, class... Args>
19+
void derived_from_specialization_impl(const Template<Args...>&);
20+
}
21+
1522
namespace Qx
1623
{
1724

@@ -505,6 +512,9 @@ concept any_of = (std::same_as<K, L> || ...);
505512
template<typename K, template <typename...> class L>
506513
concept specializes = is_specialization_of_v<K, L>;
507514

515+
template <class K, template <class...> class L>
516+
concept derived_from_specialization_of = requires(const K& k) { _QxPrivate::derived_from_specialization_impl<L>(k); };
517+
508518
// Similar interface types
509519
template<typename T>
510520
concept qassociative = specializes<T, QHash> ||

lib/utility/include/qx/utility/qx-stringliteral.h

Lines changed: 186 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,204 @@
11
#ifndef QX_STRINGLITERAL_H
22
#define QX_STRINGLITERAL_H
33

4+
// Standard Library Includes
45
#include <algorithm>
6+
#include <concepts>
7+
8+
// Qt Includes
9+
#include <QLatin1String>
10+
11+
// Intra-component Includes
12+
#include "qx/utility/qx-concepts.h"
13+
14+
/* This base class is an implementation detail that makes it much easier (if not outright
15+
* possible at all) to make concepts that need to work for all StringLiteral types while
16+
* ignoring the size parameter N. Once Clang 19+ is common (see below todo), this won't
17+
* be needed.
18+
*/
19+
/*! @cond */
20+
namespace _QxPrivate
21+
{
22+
23+
template<std::integral C>
24+
class StringLiteralBase {};
25+
26+
}
27+
/*! @endcond */
528

629
namespace Qx
730
{
831

9-
template<size_t N>
10-
struct StringLiteral
32+
// These concepts won't be needed either as per-above and todo.
33+
template<typename T>
34+
concept string_literal = derived_from_specialization_of<T, _QxPrivate::StringLiteralBase>;
35+
36+
template<typename T, typename U>
37+
concept compatible_string_literals =
38+
string_literal<T> &&
39+
string_literal<U> &&
40+
std::same_as<typename T::data_t, typename U::data_t>;
41+
42+
template<typename S, typename C>
43+
concept compatible_string_literal_operand =
44+
string_literal<S> &&
45+
std::same_as<typename S::data_t, C>;
46+
47+
template<std::integral C, size_t N>
48+
class StringLiteral : public _QxPrivate::StringLiteralBase<C>
1149
{
12-
//-Instance Fields---------------------------------------------------------------------------------------------------
13-
char value[N];
50+
//-Aliases----------------------------------------------------------------------------------------------------------
51+
public:
52+
using data_t = C;
53+
54+
using view_t = std::conditional_t<
55+
std::is_same_v<C, char>,
56+
QLatin1StringView,
57+
std::conditional_t<
58+
std::is_same_v<C, char16_t>,
59+
QStringView,
60+
void
61+
>
62+
>;
63+
64+
/*! @cond */
65+
template<size_t M> using rebind = StringLiteral<C, M>; // Impl detail
66+
/*! @endcond */
67+
68+
//-Class Variables---------------------------------------------------------------------------------------------------
69+
public:
70+
static constexpr size_t size_v = N - 1;
71+
72+
//-Instance Variables---------------------------------------------------------------------------------------------------
73+
public:
74+
/*! @cond */
75+
C _str[N]; // Must be public due to C++20 limitations
76+
/*! @endcond */
1477

1578
//-Constructor----------------------------------------------------------------------------------------------------------
16-
constexpr StringLiteral(const char (&str)[N])
17-
{
18-
std::copy_n(str, N, value);
19-
}
79+
public:
80+
constexpr StringLiteral(const C (&str)[N]) { std::copy_n(str, N, _str); }
81+
82+
//-Instance Functions----------------------------------------------------------------------------------------------------
83+
public:
84+
constexpr C* data() const { return _str; }
85+
constexpr size_t size() const { return N - 1; }
86+
constexpr view_t view() const requires (!std::same_as<view_t, void>){ return view_t(*this); }
87+
constexpr std::basic_string_view<C> std_view() const { return std::basic_string_view<C>(*this); }
88+
89+
//-Operators--------------------------------------------------------------------------------------------------------
90+
public:
91+
constexpr std::strong_ordering operator<=>(const StringLiteral& other) const = default;
92+
constexpr bool operator==(const StringLiteral& other) const = default;
93+
constexpr operator QLatin1StringView() const requires std::same_as<C, char> { return QLatin1StringView(_str, N - 1); }
94+
constexpr operator QStringView() const requires std::same_as<C, char16_t > { return QStringView(_str, N - 1); }
95+
constexpr operator std::basic_string_view<C>() const { return std::basic_string_view<C>(_str, N - 1); }
2096
};
2197

22-
template <size_t N>
23-
struct StringLiteral16
98+
template<typename StringLiteralA, typename StringLiteralB>
99+
requires compatible_string_literals<StringLiteralA, StringLiteralB>
100+
constexpr auto operator+(const StringLiteralA& a, const StringLiteralA& b)
101+
{
102+
// It's important to note here than L1 and L2 are sizes without
103+
using C = typename StringLiteralA::data_t;
104+
constexpr size_t L1 = StringLiteralA::size_v;
105+
constexpr size_t L2 = StringLiteralB::size_v;
106+
constexpr size_t R = L1 + L2 +1;
107+
using result_t = typename StringLiteralA::template rebind<R>;
108+
/* Separate buffer is an extra copy, vs just making the result string and copying
109+
* into it directly, but this avoids the cruft of having to make these all friends
110+
* with the class and the speed loss is largely irrelevant since these are used
111+
* at compile time.
112+
*/
113+
114+
115+
C buff[R] ;
116+
std::copy_n(a._str, L1, buff); // a chars
117+
std::copy_n(b._str, L2 + 1, buff + L1); // b chars + '/0'
118+
return result_t(buff);
119+
}
120+
121+
template<typename S, std::integral C, size_t N2>
122+
requires compatible_string_literal_operand<S, C>
123+
constexpr auto operator+(const S& a, const C (&b)[N2])
24124
{
25-
char16_t value[N];
125+
// It's important to note here than N2 is a size including '/0' and L1 is a size without '/0'
126+
constexpr size_t L1 = S::size_v;
127+
constexpr size_t R = L1 + N2;
128+
using result_t = typename S::template rebind<R>;
26129

27-
constexpr StringLiteral16(const char16_t (&str)[N])
28-
{
29-
std::copy_n(str, N, value);
30-
}
130+
/* Separate buffer is an extra copy, vs just making the result string and copying
131+
* into it directly, but this avoids the cruft of having to make these all friends
132+
* with the class and the speed loss is largely irrelevant since these are used
133+
* at compile time.
134+
*/
135+
C buff[R] ; // a chars + (b chars + '/0')
136+
std::copy_n(a._str, L1, buff); // a chars
137+
std::copy_n(b, N2, buff + L1); // b chars + '/0'
138+
return result_t(buff);
139+
}
140+
141+
template<std::integral C, size_t N1, typename S>
142+
requires compatible_string_literal_operand<S, C>
143+
constexpr auto operator+(const C (&a)[N1], const S& b) { return b + a; }
144+
145+
/* TOOO: We use derivations here instead of template aliases as parameter deduction for aliases
146+
* (what allows the parameter to deduce the character type and size just based on the
147+
* type of string), P1814R0, as not implemented in Clang until 19!
148+
*
149+
* Once that version isn't that new, we can switch back to using aliases which should make the
150+
* operator+() definition easier, as it will be able to look like (for example):
151+
*
152+
* template<std::integral C, size_t N1, size_t N2>
153+
* constexpr auto operator+(const StringLiteral<C, N1>& a, const StringLiteral<C, N2>& b)
154+
* {
155+
* * Separate buffer is an extra copy, vs just making the result string and copying
156+
* * into it directly, but this avoids the cruft of having to make these all friends
157+
* * with the class and the speed loss is largely irrelevant since these are used
158+
* * at compile time.
159+
* *
160+
* C buff[N1 + N2 - 1] ;
161+
* std::copy_n(a._str, N1 - 1, buff); // a chars
162+
* std::copy_n(b._str, N2, buff + (N1 - 1)); // b chars + '/0'
163+
* return StringLiteral<C, N1 + N2 - 1>(buff);
164+
* }
165+
*
166+
* instead of having to use the current constraint workaround that needs to allow derived types,
167+
* which means we can remove a bunch of boilerplate like the 'rebind' alias
168+
*/
169+
template<size_t N>
170+
struct CStringLiteral final : public StringLiteral<char, N>
171+
{
172+
constexpr CStringLiteral(const char (&str)[N]) : StringLiteral<char, N>(str) {}
173+
/*! @cond */ template<size_t M> using rebind = CStringLiteral<M>; /*! @endcond */ // Impl detail
174+
};
175+
176+
template<size_t N>
177+
struct WStringLiteral final : public StringLiteral<wchar_t, N>
178+
{
179+
constexpr WStringLiteral(const wchar_t (&str)[N]) : StringLiteral<wchar_t, N>(str) {}
180+
/*! @cond */ template<size_t M> using rebind = WStringLiteral<M>; /*! @endcond */ // Impl detail
181+
};
182+
183+
template<size_t N>
184+
struct U8StringLiteral final : public StringLiteral<char8_t, N>
185+
{
186+
constexpr U8StringLiteral(const char8_t (&str)[N]) : StringLiteral<char8_t, N>(str) {}
187+
/*! @cond */ template<size_t M> using rebind = U8StringLiteral<M>; /*! @endcond */ // Impl detail
188+
};
189+
190+
template<size_t N>
191+
struct U16StringLiteral final : public StringLiteral<char16_t, N>
192+
{
193+
constexpr U16StringLiteral(const char16_t (&str)[N]) : StringLiteral<char16_t, N>(str) {}
194+
/*! @cond */ template<size_t M> using rebind = U16StringLiteral<M>; /*! @endcond */ // Impl detail
195+
};
196+
197+
template<size_t N>
198+
struct U32StringLiteral final : public StringLiteral<char32_t, N>
199+
{
200+
constexpr U32StringLiteral(const char32_t (&str)[N]) : StringLiteral<char32_t, N>(str) {}
201+
/*! @cond */ template<size_t M> using rebind = U32StringLiteral<M>; /*! @endcond */ // Impl detail
31202
};
32203

33204
}

0 commit comments

Comments
 (0)