forked from qt-creator/qt-creator
Utils: Improve memory layout code
Actually, the layout is the same, but we move the control block. To prevent padding of the short string, we use pragma pack(1). To align the pointer again, we add some dummy data with the size of a pointer minus a control block because alignas is not working like expected on GCC. Change-Id: Ide86ace243dab5f487da63492ebac018da45098a Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
@@ -63,7 +63,7 @@
|
|||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|
||||||
template<uint Size>
|
template<uint Size>
|
||||||
class BasicSmallString
|
class alignas(16) BasicSmallString
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using const_iterator = Internal::SmallStringIterator<std::random_access_iterator_tag, const char>;
|
using const_iterator = Internal::SmallStringIterator<std::random_access_iterator_tag, const char>;
|
||||||
@@ -97,14 +97,14 @@ public:
|
|||||||
BasicSmallString(const char *string, size_type size, size_type capacity)
|
BasicSmallString(const char *string, size_type size, size_type capacity)
|
||||||
{
|
{
|
||||||
if (Q_LIKELY(capacity <= shortStringCapacity())) {
|
if (Q_LIKELY(capacity <= shortStringCapacity())) {
|
||||||
std::char_traits<char>::copy(m_data.shortString.string, string, size);
|
std::char_traits<char>::copy(m_data.shortString, string, size);
|
||||||
m_data.shortString.string[size] = 0;
|
m_data.shortString[size] = 0;
|
||||||
m_data.shortString.control.setShortStringSize(size);
|
m_data.control.setShortStringSize(size);
|
||||||
m_data.shortString.control.setIsShortString(true);
|
m_data.control.setIsShortString(true);
|
||||||
m_data.shortString.control.setIsReadOnlyReference(false);
|
m_data.control.setIsReadOnlyReference(false);
|
||||||
} else {
|
} else {
|
||||||
m_data.allocated.data.pointer = Memory::allocate(capacity + 1);
|
m_data.reference.pointer = Memory::allocate(capacity + 1);
|
||||||
std::char_traits<char>::copy(m_data.allocated.data.pointer, string, size);
|
std::char_traits<char>::copy(m_data.reference.pointer, string, size);
|
||||||
initializeLongString(size, capacity);
|
initializeLongString(size, capacity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,7 @@ public:
|
|||||||
~BasicSmallString() noexcept
|
~BasicSmallString() noexcept
|
||||||
{
|
{
|
||||||
if (Q_UNLIKELY(hasAllocatedMemory()))
|
if (Q_UNLIKELY(hasAllocatedMemory()))
|
||||||
Memory::deallocate(m_data.allocated.data.pointer);
|
Memory::deallocate(m_data.reference.pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicSmallString(const BasicSmallString &other)
|
BasicSmallString(const BasicSmallString &other)
|
||||||
@@ -182,7 +182,7 @@ public:
|
|||||||
{
|
{
|
||||||
if (Q_LIKELY(this != &other)) {
|
if (Q_LIKELY(this != &other)) {
|
||||||
if (Q_UNLIKELY(hasAllocatedMemory()))
|
if (Q_UNLIKELY(hasAllocatedMemory()))
|
||||||
Memory::deallocate(m_data.allocated.data.pointer);
|
Memory::deallocate(m_data.reference.pointer);
|
||||||
|
|
||||||
if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference()))
|
if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference()))
|
||||||
m_data = other.m_data;
|
m_data = other.m_data;
|
||||||
@@ -203,7 +203,7 @@ public:
|
|||||||
{
|
{
|
||||||
if (Q_LIKELY(this != &other)) {
|
if (Q_LIKELY(this != &other)) {
|
||||||
if (Q_UNLIKELY(hasAllocatedMemory()))
|
if (Q_UNLIKELY(hasAllocatedMemory()))
|
||||||
Memory::deallocate(m_data.allocated.data.pointer);
|
Memory::deallocate(m_data.reference.pointer);
|
||||||
|
|
||||||
m_data = std::move(other.m_data);
|
m_data = std::move(other.m_data);
|
||||||
other.m_data.reset();
|
other.m_data.reset();
|
||||||
@@ -218,9 +218,9 @@ public:
|
|||||||
BasicSmallString clonedString(m_data);
|
BasicSmallString clonedString(m_data);
|
||||||
|
|
||||||
if (Q_UNLIKELY(hasAllocatedMemory()))
|
if (Q_UNLIKELY(hasAllocatedMemory()))
|
||||||
new (&clonedString) BasicSmallString{m_data.allocated.data.pointer,
|
new (&clonedString) BasicSmallString{m_data.reference.pointer,
|
||||||
m_data.allocated.data.size,
|
m_data.reference.size,
|
||||||
m_data.allocated.data.capacity};
|
m_data.reference.capacity};
|
||||||
|
|
||||||
return clonedString;
|
return clonedString;
|
||||||
}
|
}
|
||||||
@@ -274,11 +274,11 @@ public:
|
|||||||
{
|
{
|
||||||
if (fitsNotInCapacity(newCapacity)) {
|
if (fitsNotInCapacity(newCapacity)) {
|
||||||
if (Q_UNLIKELY(hasAllocatedMemory())) {
|
if (Q_UNLIKELY(hasAllocatedMemory())) {
|
||||||
m_data.allocated.data.pointer = Memory::reallocate(m_data.allocated.data.pointer,
|
m_data.reference.pointer = Memory::reallocate(m_data.reference.pointer,
|
||||||
newCapacity + 1);
|
newCapacity + 1);
|
||||||
m_data.allocated.data.capacity = newCapacity;
|
m_data.reference.capacity = newCapacity;
|
||||||
} else if (newCapacity <= shortStringCapacity()) {
|
} else if (newCapacity <= shortStringCapacity()) {
|
||||||
new (this) BasicSmallString{m_data.allocated.data.pointer, m_data.allocated.data.size};
|
new (this) BasicSmallString{m_data.reference.pointer, m_data.reference.size};
|
||||||
} else {
|
} else {
|
||||||
const size_type oldSize = size();
|
const size_type oldSize = size();
|
||||||
newCapacity = std::max(newCapacity, oldSize);
|
newCapacity = std::max(newCapacity, oldSize);
|
||||||
@@ -286,7 +286,7 @@ public:
|
|||||||
|
|
||||||
char *newData = Memory::allocate(newCapacity + 1);
|
char *newData = Memory::allocate(newCapacity + 1);
|
||||||
std::char_traits<char>::copy(newData, oldData, oldSize);
|
std::char_traits<char>::copy(newData, oldData, oldSize);
|
||||||
m_data.allocated.data.pointer = newData;
|
m_data.reference.pointer = newData;
|
||||||
initializeLongString(oldSize, newCapacity);
|
initializeLongString(oldSize, newCapacity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,12 +307,12 @@ public:
|
|||||||
|
|
||||||
char *data() noexcept
|
char *data() noexcept
|
||||||
{
|
{
|
||||||
return Q_LIKELY(isShortString()) ? m_data.shortString.string : m_data.allocated.data.pointer;
|
return Q_LIKELY(isShortString()) ? m_data.shortString : m_data.reference.pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *data() const noexcept
|
const char *data() const noexcept
|
||||||
{
|
{
|
||||||
return Q_LIKELY(isShortString()) ? m_data.shortString.string : m_data.allocated.data.pointer;
|
return Q_LIKELY(isShortString()) ? m_data.shortString : m_data.reference.pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *constData() const noexcept
|
const char *constData() const noexcept
|
||||||
@@ -442,15 +442,15 @@ public:
|
|||||||
size_type size() const noexcept
|
size_type size() const noexcept
|
||||||
{
|
{
|
||||||
if (!isShortString())
|
if (!isShortString())
|
||||||
return m_data.allocated.data.size;
|
return m_data.reference.size;
|
||||||
|
|
||||||
return m_data.shortString.control.shortStringSize();
|
return m_data.control.shortStringSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_type capacity() const noexcept
|
size_type capacity() const noexcept
|
||||||
{
|
{
|
||||||
if (!isShortString())
|
if (!isShortString())
|
||||||
return m_data.allocated.data.capacity;
|
return m_data.reference.capacity;
|
||||||
|
|
||||||
return shortStringCapacity();
|
return shortStringCapacity();
|
||||||
}
|
}
|
||||||
@@ -603,11 +603,7 @@ public:
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr
|
constexpr size_type shortStringSize() const { return m_data.control.shortStringSize(); }
|
||||||
size_type shortStringSize() const
|
|
||||||
{
|
|
||||||
return m_data.shortString.control.shortStringSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
static
|
||||||
BasicSmallString join(std::initializer_list<SmallStringView> list)
|
BasicSmallString join(std::initializer_list<SmallStringView> list)
|
||||||
@@ -721,13 +717,13 @@ unittest_public:
|
|||||||
constexpr
|
constexpr
|
||||||
bool isShortString() const noexcept
|
bool isShortString() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.shortString.control.isShortString();
|
return m_data.control.isShortString();
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr
|
constexpr
|
||||||
bool isReadOnlyReference() const noexcept
|
bool isReadOnlyReference() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.shortString.control.isReadOnlyReference();
|
return m_data.control.isReadOnlyReference();
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr
|
constexpr
|
||||||
@@ -739,7 +735,7 @@ unittest_public:
|
|||||||
bool fitsNotInCapacity(size_type capacity) const noexcept
|
bool fitsNotInCapacity(size_type capacity) const noexcept
|
||||||
{
|
{
|
||||||
return (isShortString() && capacity > shortStringCapacity())
|
return (isShortString() && capacity > shortStringCapacity())
|
||||||
|| (!isShortString() && capacity > m_data.allocated.data.capacity);
|
|| (!isShortString() && capacity > m_data.reference.capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
@@ -802,12 +798,12 @@ private:
|
|||||||
|
|
||||||
constexpr void initializeLongString(size_type size, size_type capacity)
|
constexpr void initializeLongString(size_type size, size_type capacity)
|
||||||
{
|
{
|
||||||
m_data.allocated.data.pointer[size] = 0;
|
m_data.reference.pointer[size] = 0;
|
||||||
m_data.allocated.data.size = size;
|
m_data.reference.size = size;
|
||||||
m_data.allocated.data.capacity = capacity;
|
m_data.reference.capacity = capacity;
|
||||||
m_data.shortString.control.setShortStringSize(0);
|
m_data.control.setShortStringSize(0);
|
||||||
m_data.shortString.control.setIsReference(true);
|
m_data.control.setIsReference(true);
|
||||||
m_data.shortString.control.setIsReadOnlyReference(false);
|
m_data.control.setIsReadOnlyReference(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
char &at(size_type index)
|
char &at(size_type index)
|
||||||
@@ -945,9 +941,9 @@ private:
|
|||||||
void setSize(size_type size)
|
void setSize(size_type size)
|
||||||
{
|
{
|
||||||
if (isShortString())
|
if (isShortString())
|
||||||
m_data.shortString.control.setShortStringSize(size);
|
m_data.control.setShortStringSize(size);
|
||||||
else
|
else
|
||||||
m_data.allocated.data.size = size;
|
m_data.reference.size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@@ -96,56 +96,17 @@ private:
|
|||||||
ControlType m_isReference : 1;
|
ControlType m_isReference : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<uint MaximumShortStringDataAreaSize>
|
struct ReferenceLayout
|
||||||
struct alignas(16) AllocatedLayout
|
{ union {
|
||||||
{
|
const char *constPointer;
|
||||||
struct Data
|
|
||||||
{
|
|
||||||
char *pointer;
|
char *pointer;
|
||||||
|
};
|
||||||
size_type size;
|
size_type size;
|
||||||
size_type capacity;
|
size_type capacity;
|
||||||
};
|
};
|
||||||
|
|
||||||
ControlBlock<MaximumShortStringDataAreaSize> control;
|
|
||||||
Data data;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<uint MaximumShortStringDataAreaSize>
|
|
||||||
struct alignas(16) ReferenceLayout
|
|
||||||
{
|
|
||||||
constexpr ReferenceLayout() noexcept = default;
|
|
||||||
constexpr ReferenceLayout(const char *stringPointer,
|
|
||||||
size_type size,
|
|
||||||
size_type capacity) noexcept
|
|
||||||
: control(0, true, true),
|
|
||||||
data{stringPointer, size, capacity}
|
|
||||||
{}
|
|
||||||
|
|
||||||
struct Data
|
|
||||||
{
|
|
||||||
const char *pointer;
|
|
||||||
size_type size;
|
|
||||||
size_type capacity;
|
|
||||||
};
|
|
||||||
|
|
||||||
ControlBlock<MaximumShortStringDataAreaSize> control;
|
|
||||||
Data data;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<uint MaximumShortStringDataAreaSize>
|
|
||||||
struct alignas(16) ShortStringLayout
|
|
||||||
{
|
|
||||||
constexpr ShortStringLayout(
|
|
||||||
typename ControlBlock<MaximumShortStringDataAreaSize>::SizeType shortStringSize) noexcept
|
|
||||||
: control(shortStringSize, false, false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
ControlBlock<MaximumShortStringDataAreaSize> control;
|
|
||||||
char string[MaximumShortStringDataAreaSize];
|
|
||||||
};
|
|
||||||
|
|
||||||
template<uint MaximumShortStringDataAreaSize, typename = void>
|
template<uint MaximumShortStringDataAreaSize, typename = void>
|
||||||
struct StringDataLayout
|
struct alignas(16) StringDataLayout
|
||||||
{
|
{
|
||||||
static_assert(MaximumShortStringDataAreaSize >= 15, "Size must be greater equal than 15 bytes!");
|
static_assert(MaximumShortStringDataAreaSize >= 15, "Size must be greater equal than 15 bytes!");
|
||||||
static_assert(MaximumShortStringDataAreaSize < 32, "Size must be less than 32 bytes!");
|
static_assert(MaximumShortStringDataAreaSize < 32, "Size must be less than 32 bytes!");
|
||||||
@@ -156,17 +117,19 @@ struct StringDataLayout
|
|||||||
StringDataLayout() noexcept { reset(); }
|
StringDataLayout() noexcept { reset(); }
|
||||||
|
|
||||||
constexpr StringDataLayout(const char *string, size_type size) noexcept
|
constexpr StringDataLayout(const char *string, size_type size) noexcept
|
||||||
: reference(string, size, 0)
|
: control{0, true, true}
|
||||||
|
, reference{{string}, size, 0}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
template<size_type Size>
|
template<size_type Size>
|
||||||
constexpr StringDataLayout(const char (&string)[Size]) noexcept
|
constexpr StringDataLayout(const char (&string)[Size]) noexcept
|
||||||
{
|
{
|
||||||
if constexpr (Size <= MaximumShortStringDataAreaSize) {
|
if constexpr (Size <= MaximumShortStringDataAreaSize) {
|
||||||
shortString = {Size - 1};
|
control = {Size - 1, false, false};
|
||||||
for (size_type i = 0; i < Size; ++i)
|
for (size_type i = 0; i < Size; ++i)
|
||||||
shortString.string[i] = string[i];
|
shortString[i] = string[i];
|
||||||
} else {
|
} else {
|
||||||
|
control = {0, true, true};
|
||||||
reference = {string, Size - 1, 0};
|
reference = {string, Size - 1, 0};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,19 +141,26 @@ struct StringDataLayout
|
|||||||
|
|
||||||
constexpr void reset()
|
constexpr void reset()
|
||||||
{
|
{
|
||||||
shortString.control = ControlBlock<MaximumShortStringDataAreaSize>();
|
control = ControlBlock<MaximumShortStringDataAreaSize>();
|
||||||
shortString.string[0] = '\0';
|
shortString[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma pack(push)
|
||||||
|
#pragma pack(1)
|
||||||
|
ControlBlock<MaximumShortStringDataAreaSize> control;
|
||||||
union {
|
union {
|
||||||
AllocatedLayout<MaximumShortStringDataAreaSize> allocated;
|
char shortString[MaximumShortStringDataAreaSize];
|
||||||
ReferenceLayout<MaximumShortStringDataAreaSize> reference;
|
struct
|
||||||
ShortStringLayout<MaximumShortStringDataAreaSize> shortString;
|
{
|
||||||
|
char dummy[sizeof(void *) - sizeof(ControlBlock<MaximumShortStringDataAreaSize>)];
|
||||||
|
ReferenceLayout reference;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
};
|
||||||
|
|
||||||
template<uint MaximumShortStringDataAreaSize>
|
template<uint MaximumShortStringDataAreaSize>
|
||||||
struct StringDataLayout<MaximumShortStringDataAreaSize,
|
struct alignas(16) StringDataLayout<MaximumShortStringDataAreaSize,
|
||||||
std::enable_if_t<MaximumShortStringDataAreaSize >= 32>>
|
std::enable_if_t<MaximumShortStringDataAreaSize >= 32>>
|
||||||
{
|
{
|
||||||
static_assert(MaximumShortStringDataAreaSize > 31, "Size must be greater than 31 bytes!");
|
static_assert(MaximumShortStringDataAreaSize > 31, "Size must be greater than 31 bytes!");
|
||||||
@@ -203,17 +173,19 @@ struct StringDataLayout<MaximumShortStringDataAreaSize,
|
|||||||
StringDataLayout() noexcept { reset(); }
|
StringDataLayout() noexcept { reset(); }
|
||||||
|
|
||||||
constexpr StringDataLayout(const char *string, size_type size) noexcept
|
constexpr StringDataLayout(const char *string, size_type size) noexcept
|
||||||
: reference(string, size, 0)
|
: control{0, true, true}
|
||||||
|
, reference{string, size, 0}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
template<size_type Size>
|
template<size_type Size>
|
||||||
constexpr StringDataLayout(const char (&string)[Size]) noexcept
|
constexpr StringDataLayout(const char (&string)[Size]) noexcept
|
||||||
{
|
{
|
||||||
if constexpr (Size <= MaximumShortStringDataAreaSize) {
|
if constexpr (Size <= MaximumShortStringDataAreaSize) {
|
||||||
shortString = {Size - 1};
|
control = {Size - 1, false, false};
|
||||||
for (size_type i = 0; i < Size; ++i)
|
for (size_type i = 0; i < Size; ++i)
|
||||||
shortString.string[i] = string[i];
|
shortString[i] = string[i];
|
||||||
} else {
|
} else {
|
||||||
|
control = {0, true, true};
|
||||||
reference = {string, Size - 1, 0};
|
reference = {string, Size - 1, 0};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,11 +195,9 @@ struct StringDataLayout<MaximumShortStringDataAreaSize,
|
|||||||
StringDataLayout &operator=(const StringDataLayout &other) noexcept
|
StringDataLayout &operator=(const StringDataLayout &other) noexcept
|
||||||
{
|
{
|
||||||
constexpr auto controlBlockSize = sizeof(ControlBlock<MaximumShortStringDataAreaSize>);
|
constexpr auto controlBlockSize = sizeof(ControlBlock<MaximumShortStringDataAreaSize>);
|
||||||
auto shortStringLayoutSize = other.shortString.control.stringSize() + controlBlockSize;
|
auto shortStringLayoutSize = other.control.stringSize() + controlBlockSize;
|
||||||
constexpr auto referenceLayoutSize = sizeof(ReferenceLayout<MaximumShortStringDataAreaSize>);
|
constexpr auto referenceLayoutSize = sizeof(ReferenceLayout);
|
||||||
std::memcpy(&shortString,
|
std::memcpy(this, &other, std::max(shortStringLayoutSize, referenceLayoutSize));
|
||||||
&other.shortString,
|
|
||||||
std::max(shortStringLayoutSize, referenceLayoutSize));
|
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -239,16 +209,23 @@ struct StringDataLayout<MaximumShortStringDataAreaSize,
|
|||||||
|
|
||||||
constexpr void reset()
|
constexpr void reset()
|
||||||
{
|
{
|
||||||
shortString.control = ControlBlock<MaximumShortStringDataAreaSize>();
|
control = ControlBlock<MaximumShortStringDataAreaSize>();
|
||||||
shortString.string[0] = '\0';
|
shortString[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma pack(push)
|
||||||
|
#pragma pack(1)
|
||||||
|
ControlBlock<MaximumShortStringDataAreaSize> control;
|
||||||
union {
|
union {
|
||||||
AllocatedLayout<MaximumShortStringDataAreaSize> allocated;
|
char shortString[MaximumShortStringDataAreaSize];
|
||||||
ReferenceLayout<MaximumShortStringDataAreaSize> reference;
|
struct
|
||||||
ShortStringLayout<MaximumShortStringDataAreaSize> shortString;
|
{
|
||||||
|
char dummy[sizeof(void *) - sizeof(ControlBlock<MaximumShortStringDataAreaSize>)];
|
||||||
|
ReferenceLayout reference;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
@@ -58,12 +58,12 @@ public:
|
|||||||
|
|
||||||
const char *data() const noexcept
|
const char *data() const noexcept
|
||||||
{
|
{
|
||||||
return Q_LIKELY(isShortString()) ? m_data.shortString.string : m_data.allocated.data.pointer;
|
return Q_LIKELY(isShortString()) ? m_data.shortString : m_data.reference.constPointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_type size() const noexcept
|
size_type size() const noexcept
|
||||||
{
|
{
|
||||||
return Q_LIKELY(isShortString()) ? m_data.shortString.control.shortStringSize() : m_data.allocated.data.size;
|
return Q_LIKELY(isShortString()) ? m_data.control.shortStringSize() : m_data.reference.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr
|
constexpr
|
||||||
@@ -94,16 +94,12 @@ public:
|
|||||||
return Internal::StringDataLayout<Size>::shortStringCapacity();
|
return Internal::StringDataLayout<Size>::shortStringCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr
|
constexpr bool isShortString() const noexcept { return m_data.control.isShortString(); }
|
||||||
bool isShortString() const noexcept
|
|
||||||
{
|
|
||||||
return m_data.shortString.control.isShortString();
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr
|
constexpr
|
||||||
bool isReadOnlyReference() const noexcept
|
bool isReadOnlyReference() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.shortString.control.isReadOnlyReference();
|
return m_data.control.isReadOnlyReference();
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr
|
constexpr
|
||||||
|
Reference in New Issue
Block a user