6

Is there a way to define as static constexpr string literal member in Qt? I.e. something like the following:

class X
{
   static constexpr QString tag = "mytag";
};
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Silicomancer
  • 8,604
  • 10
  • 63
  • 130

4 Answers4

7

I did what Jesper recommended in his comment, I used QLatin1String. But I used it through a small helper class to avoid the strlen() call in QLatin1String:

struct ConstLatin1String : public QLatin1String
{
    constexpr ConstLatin1String(const char* const s) : 
        QLatin1String(s, static_cast<int>(std::char_traits<char>::length(s))) {}
};

This allows to do:

static constexpr ConstLatin1String mystring = "foo";
Silicomancer
  • 8,604
  • 10
  • 63
  • 130
  • I think it has memory leak: https://code.woboq.org/qt5/qtbase/src/corelib/text/qstring.h.html#QLatin1String because QLatin1String has not virtual destructor. – H.M Feb 14 '22 at 20:10
  • @H.M No memory leak here. We would get a problem only when implementing a destructor for ConstLatin1String and only when treating it as a polymorphic (i.e. deleting it by pointer to QLatin1String). – Silicomancer Feb 15 '22 at 11:12
2

Inspired by Silicomancer answer but works with c++11

#include <QLatin1String>

namespace detail {

// Helper function for getting compile-time string literal's length 
// Given from: https://stackoverflow.com/q/27498592/ 
// c++11 analogue of: std::char_traits<char>::length(str); from c++14
constexpr std::size_t constLength(const char* str)
{
    return ((*str) == '\0') ? 0 : constLength(str + 1) + 1;
}

} // namespace detail

// Constructs compile-time <QLatin1String> from string literal
constexpr QLatin1String make_string(const char* str)
{
    return QLatin1String(str, detail::constLength(str));
}

// Alternate implementation for string literals only, without 
// recursive function call (reduces compilation time), using 
// known in compile-time size.
template <std::size_t SIZE>
constexpr QLatin1String make_string_from_literal(const char (&str)[SIZE])
{
    return QLatin1String(str, SIZE);
}

// Example of usage:
static constexpr QLatin1String STR = make_string("hello_world");

Testing

// Helper function for compile-time strings comparison
// Given from https://stackoverflow.com/a/27490954/
constexpr bool strings_equal(const char* a, const char* b) {
    return (*a == *b) && (*a == '\0' || strings_equal(a + 1, b + 1));
}

// Compile-time in-place construction check
static_assert( strings_equal(make_string("foo").data(), "foo"), "test failed");

// Compile-time constant construction check
static constexpr QLatin1String STR = make_string("bar");
static_assert( strings_equal(STR.data(), "bar"), "test failed");

Addition for inspiration

It's perfectly works with compile-time hashing, like in that brilliant answer, which based on (that and that)

// Note: `len` must be without null-character sign
constexpr uint32_t crc32(const char* str, std::size_t len) {
    return detail::crc32(len - 1, str) ^ 0xFFFFFFFF;
}

template <std::size_t LEN>
constexpr std::uint32_t crc32(const char (&str)[LEN]) {
    return crc32(str, LEN - 1);
}

// Overload for <QLatin1String>
//   Notice that, next methods: QLatin1String::{.data(), .size()} 
//   marked as constexpr only since Qt 5.6. 
constexpr std::uint32_t crc32(const QLatin1String& str) {
    return crc32(str.data(), str.size());
}

// ------------------------------------------------------------

// Test with explicitely specified literal size
static_assert( crc32("hello world", 11) == 0xd4a1185,  "CRC32 test failed");
static_assert( crc32("foo bar",      7) == 0xbe460134, "CRC32 test failed");

// Test with implicitly calculated literal size
static_assert( crc32("hello world") == 0xd4a1185,  "CRC32 test failed");
static_assert( crc32("foo bar")     == 0xbe460134, "CRC32 test failed");

// Test with convenient overload for <QLatin1String>
static constexpr QLatin1String STR_1 = make_string("hello world");
static constexpr QLatin1String STR_2 = make_string("foo bar");
static_assert( crc32(STR_1) == 0xd4a1185,  "CRC32 test failed");
static_assert( crc32(STR_2) == 0xbe460134, "CRC32 test failed");

That allow compile-time string matching via switch-case. Both switch cases are equal and computed in compile-time :)

    1. string constructed in compile-time, which allows to use it in any cases, and compute hash in place, where it needed.
    1. pre-computed hash from string. string not exists.
    1. inplace usage without any ceremony :D
static constexpr QLatin1String MATCH_1 = make_string("value_1"); // 1)
static constexpr auto MATCH_2 = crc32( make_string("value_2") ); // 2)

const QLatin1String str = /* ... */;
switch( crc32(str) )
{
    case crc32(MATCH_1):   { do_something_for_value_1(); } break; // 1)
    case MATCH_2:          { do_something_for_value_2(); } break; // 2)
    case crc32("value_3"): { do_something_for_value_3(); } break; // 3)
}
Inobelar
  • 59
  • 8
1

Considering that QString is a (potentially) heap-allocated string, and you can't allocate memory in constexpr code, no. It's no more valid at compile-time than using std::string.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
0
constexpr QStringView tag = u"mytag";
magrif
  • 396
  • 4
  • 20