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";
};
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";
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 :)
string constructed in compile-time, which allows to use it in any cases, and compute hash in place, where it needed.
pre-computed hash from string. string not exists.
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)
}
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.
constexpr QStringView tag = u"mytag";
Related
Context:
In my company we generate a lot of types based on IDL files. Some of the types require special logic so they are handcoded but follow the same pattern as the generated ones. We have a function which all types must implement which is a name function. This will return the type name as a char* string and the function is constexpr.
Problem:
The problem is regarding collections which could contain other collections nested potentially N number of times. I therefore am trying to concatenate two or more char* strings at compile time.
Pseudocode of what I want to achieve:
template <typename T>
constexpr char* name()
{
constexpr char* collectionName = "std::vector";
constexpr char* containedTypeName = name<T>();
return concat(collectionName, "<", containedTypeName, ">");
}
Note:
There are examples out there which does something like this but is done with char[] or the use of static variables.
The question:
How can I make a constexpr function which return a char* which consists of two or more concatenated char* strings at compile time? I am bound to C++17.
From constexpr you cannot return char* which is constructed there... You must return some compile time known(also its size) constant thingy.
A possible solution could be something like:
#include <cstring>
// Buffer to hold the result
struct NameBuffer
{
// Hardcoded 128 bytes!!!!! Carefully choose the size!
char data[128];
};
// Copy src to dest, and return the number of copied characters
// You have to implement it since std::strcpy is not constexpr, no big deal.
constexpr int constexpr_strcpy(char* dest, const char* src);
//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr NameBuffer name()
{
// We will return this
NameBuffer buf{};
constexpr const char* collectionName = "std::vector";
constexpr const char* containedTypeName = "dummy";
// Copy them one by one after each other
int n = constexpr_strcpy(buf.data, collectionName);
n += constexpr_strcpy(buf.data + n, "<");
n += constexpr_strcpy(buf.data + n, containedTypeName);
n += constexpr_strcpy(buf.data + n, ">");
// Null terminate the buffer, or you can store the size there or whatever you want
buf.data[n] = '\0';
return buf;
}
Demo
And since the returned char* is only depends on the template parameter in your case, you can create templated variables, and create a char* to them, and it can act like any other char*...
EDIT:
I have just realized that your pseudo code will never work!! Inside name<T>() you are trying to call name<T>().
You must redesign this!!! But! With some hack you can determine the size at compile time somehow for example like this:
#include <cstring>
#include <iostream>
template<std::size_t S>
struct NameBuffer
{
char data[S];
};
// Copy src to dest, and return the number of copied characters
constexpr int constexpr_strcpy(char* dest, const char* src)
{
int n = 0;
while((*(dest++) = *(src++))){ n++; }
return n;
}
// Returns the len of str without the null term
constexpr int constexpr_strlen(const char* str)
{
int n = 0;
while(*str) { str++; n++; }
return n;
}
// This template parameter does nothing now...
// I left it there so you can see how to create the template variable stuff...
//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr auto createName()
{
constexpr const char* collectionName = "std::vector";
constexpr const char* containedTypeName = "dummy";
constexpr std::size_t buff_size = constexpr_strlen(collectionName) +
constexpr_strlen(containedTypeName) +
2; // +1 for <, +1 for >
/// +1 for the nullterm
NameBuffer<buff_size + 1> buf{};
/// I'm lazy to rewrite, but now we already calculated the lengths...
int n = constexpr_strcpy(buf.data, collectionName);
n += constexpr_strcpy(buf.data + n, "<");
n += constexpr_strcpy(buf.data + n, containedTypeName);
n += constexpr_strcpy(buf.data + n, ">");
buf.data[n] = '\0';
return buf;
}
// Create the buffer for T
template<typename T>
static constexpr auto name_buff_ = createName<T>();
// point to the buffer of type T. It can be a function too as you wish
template<typename T>
static constexpr const char* name = name_buff_<T>.data;
int main()
{
// int is redundant now, but this is how you could use this
std::cout << name<int> << '\n';
return 0;
}
Demo
This is a follow up of my question given here. In the end I want to create a constexpr std::array containing text with an appended running index.
I wanted to try a different approach than in the previous question.
Nearly everything, what I do in the below code is constexpr. But maybe, it is simply the old problem of returning a pointer to a no longer existing variable. But, I doubt this.
Please see the following code, where the not working line in function main is marked.
#include <iostream>
#include <algorithm>
#include <iterator>
#include <array>
#include <string>
// Some example text
static constexpr const char BaseString[]{ "text" };
// To create something like "text123" as constexpr
template <const size_t numberToConvert, const char* Text>
class Converter {
public:
// Some helper variables
static constexpr size_t TextLength{ std::char_traits<char>::length(Text) };
static constexpr size_t NumberOfDigits{ ([]() constexpr noexcept {size_t result = 0; int temp = numberToConvert; for (; temp != 0; temp /= 10) ++result; return result; }()) };
static constexpr size_t ArrayLength{ (numberToConvert ? 1u : 2u) + NumberOfDigits + TextLength };
// Here we will build the text
char buf[ArrayLength]{};
// Constructor: Convert number to character digits
constexpr Converter() noexcept {
size_t i{ 0 }; for (; i < TextLength; ++i) buf[i] = Text[i]; // Copy text
if (numberToConvert == 0) buf[i] = '0';
else {
i = NumberOfDigits + TextLength - 1; // Convert number to character digits
int number = numberToConvert; for (; number; number /= 10)
buf[i--] = number % 10 + '0';
}
}
// cast operator
constexpr operator const char* () const noexcept { return buf; }
// For test purposes
constexpr const char* data() const noexcept { return buf; }
};
// Driver program
int main() {
// Temporaray constexprs
constexpr Converter<123, BaseString> conv123{}; // Default construction
constexpr auto conv2 = Converter<2, BaseString>(); // Assign / copy
// Build constexpr std::array and initialize it with constexprs
constexpr std::array< const char*, 2> convArray1{ conv123, conv2 };
// Show that it works
std::copy(convArray1.begin(), convArray1.end(), std::ostream_iterator<const char*>(std::cout, "\n"));
// Does compile, but not work. Array will be initialized with nullptr *******************************************
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(), Converter<2, BaseString>().data() };
std::cout << convArray2[0] << '\n' << convArray2[0] << '\n';
return 0;
}
So, I can create constexpr "values" with my templated class. Those values can be used in the "initializer" list for a constexpr std::array. But, if I want to use my class directly in the initializer list, then it compiles, but stores only nullptrs. Output of the program is:
text123
text2
╠╠╠╠╠╠╠╠╠╠╠╠╠╠<½7
╠╠╠╠╠╠╠╠╠╠╠╠╠╠<½7
Why does this happen? Or, is there a solution?
Compiled with Microsoft Visual Studio Community 2019, Version 16.8.2, C++17, Debug, X86
Your code generating compile time dangling pointers (which should be impossible) on MSVC.
To fix:
template <const size_t numberToConvert, const char* Text>
class Converter {
// blah
std::array<char, ArrayLength> buf{};
constexpr operator std::array<char, ArrayLength>() const { return buf; }
constexpr std::array<char, ArrayLength> get() const { return *this; }
};
and remove other conversion operators and data method.
template<const size_t numberToConvert, const char* Text>
constexpr auto Converted = Converter<numberToConvert, Text>{}.get();
and now use Converted<blah...>.data() to get the pointers you want.
If you really want implicit conversion to character pointer:
template<const size_t numberToConvert, const char* Text>
struct Convertest {
constexpr operator char const*() const { return Converted<numberToConvert,Text>.data(); }
};
rename classes and variables however you like.
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(),
Converter<2, BaseString>().data() };
Here, you are storing pointers to temporary variables - both Converter objects seize to exist after ;. Making dereferencing the pointers UB.
Clang rejects such code giving quite helpful message:
<source>:51:43: note: pointer to subobject of temporary is not a constant expression
<source>:51:55: note: temporary created here
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(), Converter<2, BaseString>().data() };
^
2 errors generated.
Execution build compiler returned: 1
I am not sure about the specific constexpr rules but the code is unsafe even if it would compile.
In Cpp-Reference you can see that
A constant expression is either
[...]
a prvalue core constant expression whose value satisfies the following constraints: [...] if the value is of pointer type, it holds - address of an object with static storage duration
So, for convArray1
constexpr std::array< const char*, 2> convArray1{ conv123, conv2 };
you have to make static conv123 and conv2
// VVVVVV
static constexpr Converter<123, BaseString> conv123{};
static constexpr auto conv2 = Converter<2, BaseString>();
// ^^^^^^
because you can't have a constant expression from a pointer with not static storage.
For convArray2
constexpr std::array< const char*, 2> convArray2{ Converter<2, BaseString>(), Converter<2, BaseString>().data() };
I don't see a way to get a constexpr object from pointers inside temporary objects.
I'm using C++17 with templates and recursion to replace the C Va_Args.
Currently only floats are supported, more types are following once float is working ;)
class CWrite
{
public:
template<typename NextT, typename ...RestT>
static std::string Format(NextT next, RestT ... rest);
private:
template<typename T>
static constexpr bool is_float = std::is_same_v<T, float>;
template<typename T>
static constexpr bool IsValidParam();
template<typename LastT>
static std::string Format(LastT last);
///Empty param case
static std::string Format();
};
// +++++++++++++++++++ Implementation ++++++++++++++++++++++++++
template<typename T>
constexpr bool CWrite::IsValidParam()
{
bool bRes = false;
bRes |= is_float<T>;
return bRes;
}
template<typename NextT, typename ...RestT>
std::string CWrite::Format(NextT next, RestT ... rest)
{
std::string strRes = Format(next);
strRes += Format(rest...);
return strRes;
}
template<typename LastT>
std::string CWrite::Format(LastT last)
{
std::string strRes;
if (is_float<LastT>)
{
strRes = "float:";
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
strRes += buffer;
}
return strRes;
}
///Empty param case
std::string CWrite::Format()
{
return "";
}
calling this with
std::string strRes = CWrite::Format(1.0f, 2.0f, 3.0f, 4.0f, 5);
results in a warning for snprintf()
format '%f' expects argument of type 'double', but argument 4 has type 'int'
I'd expect that IsValidParam returns false for the last argument which should be an int.
https://onlinegdb.com/B1A72GHgU
Could you help me out here?
Did i miss something here?
If you can use C++17, you should use if constexpr in the following function
template<typename LastT>
std::string CWrite::Format(LastT last)
{
std::string strRes;
// VVVVVVVVV <-- add "constexpr" here
if constexpr (is_float<LastT>)
{
strRes = "float:";
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
strRes += buffer;
}
return strRes;
}
The problem is that, using a simple if instead if constexpr, the compiler has to compile the statement (the part inside the { ... }) also when is_float<LastT> is false.
If you can't use C++17... I suppose you can differentiate the function through overloading
std::string CWrite::Format (float last)
{
std::string strRes { "float:" };
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
return strRes += buffer;
}
std::string CWrite::Format (int last)
{
std::string strRes { "int:" };
char buffer[10] = { };
snprintf(buffer, 10, "%i", last);
return strRes += buffer;
}
max66's answer addresses the reason why your method has a problem with the format string and how to fix it. Basically you just need some way of picking a different format string based on the type of the value being formatted.
However, I'd like to point out another flaw: you assume that any given value will only require 9 characters to convert into a string. For very large values (e.g. 1e22) this will fail. GCC will actually issue you a warning if it can determine this at compile time.
Additionally, your current implementation allocates many strings and recursively appends them together. This is - of course - highly inefficient and diminishes the speed of the printf family of functions to the point that it's not really worth using them.
Also your solution doesn't check for format errors (snprintf() returns negative in this case). And in such cases you may be appended undefined memory onto your string, as I'm not certain the C standard guarantees to null terminate the buffer on failure cases (but it might).
My solution is to have a function that formats a given argument in-place onto the end of a std::string. Additionally it handles format errors and cases where 9 bytes is insufficient to hold the formatted value.
Additionally I impose SFINAE restrictions on the argument types to ensure it can only be called with types that we support.
Here's my solution with comments to explain what does what and why:
#include <string>
#include <type_traits>
#include <iostream>
// checks if T is a type we support
template<typename T>
inline constexpr bool allowed_type = std::is_floating_point_v<T> || std::is_integral_v<T>;
// the initial amount of space for stringifying each argument
constexpr std::size_t APPEND_PADDING = 20;
// returns the appropriate format string for type T (T assumed to be supported)
template<typename T>
const char *fmt_string()
{
if constexpr (std::is_floating_point_v<T>) return "%f";
else return "%d";
}
// stringifys val onto the end of str (T assumed to be supported)
template<typename T>
void append(std::string &str, T val)
{
std::size_t prev_size = str.size(); // remember the previous size of str
str.resize(prev_size + APPEND_PADDING); // allocate the space we need
const char *fmt = fmt_string<T>(); // get the format string to use
// format the value and check the save the return value
int res = snprintf(&str[prev_size], APPEND_PADDING, fmt, val);
// on format error, just skip it (or )
if (res < 0) str.resize(prev_size);
// if we didn't have enough room we need to try again with the correct size
if ((std::size_t)res >= APPEND_PADDING)
{
str.resize(prev_size + res + 1); // make space for the characters we need and the null terminator
snprintf(&str[prev_size], res + 1, fmt, val); // format the string again (this time will work)
str.pop_back(); // remove the null terminator
}
// otherwise we had enough room, so just truncate to the written characters
else str.resize(prev_size + res);
}
// formats all of args into a single string (only allows supported types)
template<typename ...Args, std::enable_if_t<(allowed_type<Args> && ...), int> = 0>
std::string format(Args ...args)
{
std::string str; // create an empty buffer string to store the result
str.reserve(sizeof...(args) * APPEND_PADDING); // predict how much space we'll need for everything
int _[] = { (append(str, args), 0)... }; // append all the args to str one at a time
(void)_; // suppress unused variable warnings (will just be optimized away)
return str;
}
int main()
{
std::cout << format(1, 2, 2.3, 3, 4.4, 5, 1e22) << '\n';
}
Note that this runs all the formatted strings together with no separation. Fixing this would be as simple as changing the format strings returned from fmt_string().
I used different function names than you did, but you get the idea. format() is the function you would use.
The Ghostscript interpreter API has a function
GSDLLEXPORT int GSDLLAPI gsapi_init_with_args(void *instance, int argc, char **argv)
The final argument argv is a pointer to an array of C strings, which are interpreted as command-line arguments. I obviously cannot change the signature of the function gsapi_init_with_args to take a const char ** argument instead.
If I were willing to ignore (or silence) the deprecated conversion from string constant to 'char*' warning, then I would write simply
char *gs_argv[] = {"", "-dNOPAUSE", "-dBATCH", ...};
and pass gs_argv as the final argument. But I would prefer to fix my code so that I am not relying on an external function to behave in the way I expect it to (and effectively treat gs_argv as const char**).
Is there any simple way to declare gs_argv as an array of pointers to (non-const) C strings, and initialize its elements with string literals? (That is, using a similar approach to how I can initialize a single C string: using char c_str[] = "abc".) The best I can think of is to use
const char *gs_argv0[] = {"", "-dNOPAUSE", "-dBATCH", ...};
and then copy the contents, element by element, into gs_argv.
Please note that I understand why the compiler gives this warning (and have read the answers to, among others, this question). I am asking for a solution, rather than an explanation.
You can use:
char arg1[] = "";
char arg2[] = "-dNOPAUSE";
char arg3[] = "-dBATCH";
char* gs_argv0[] = {arg1, arg2, arg3, NULL};
int argc = sizeof(gs_argv0)/sizeof(gs_argv0[0]) - 1;
gsapi_init_with_args(instance, argc, gs_argv0)
Create copies of the string literals using strdup. This is more verbose, but fixes the warning.
char* gs_argv0[NARGS];
gs_argv0[0] = strdup("");
gs_argv0[1] = strdup("-dNOPAUSE");
// ...
Note that you will also need to free the memory allocated by strdup if you want to prevent leaks.
You might also want to add a comment to your code saying why you are doing this, to make it clear for future readers.
If you can guarantee that the function will not modify the non-const parameter, then it is acceptable to use const_cast in this situation.
A C++14 solution.
#define W(x) \
(([](auto& s)->char* \
{ \
static char r[sizeof(s)]; \
strcpy (r, s); \
return r; \
})(x))
char* argv[] =
{ W("--foo=bar",
W("baz"),
nullptr
};
Since this code requires C++11, there's a lower cost C++11 solution in another answer below. I'm leaving this one for posterity.
There are pretty much two choices: ignore it and const_cast, or do the right thing. Since this is modern C++, you're supposed to have nice, RAII classes. Thus, the simplest, safest thing to do is to safely wrap such an array.
// https://github.com/KubaO/stackoverflown/tree/master/questions/args-cstrings-32484688
#include <initializer_list>
#include <type_traits>
#include <cstdlib>
#include <cassert>
#include <vector>
class Args {
struct str_vector : std::vector<char*> {
~str_vector() { for (auto str : *this) free(str); }
} m_data;
void append_copy(const char * s) {
assert(s);
auto copy = strdup(s);
if (copy) m_data.push_back(copy); else throw std::bad_alloc();
}
public:
Args(std::initializer_list<const char*> l) {
for (auto str : l) append_copy(str);
m_data.push_back(nullptr);
}
template <std::size_t N>
Args(const char * const (&l)[N]) {
for (auto str : l) append_copy(str);
m_data.push_back(nullptr);
}
/// Initializes the arguments with a null-terminated array of strings.
template<class C, typename = typename std::enable_if<std::is_same<C, char const**>::value>::type>
Args(C l) {
while (*l) append_copy(*l++);
m_data.push_back(nullptr);
}
/// Initializes the arguments with an array of strings with given number of elements.
Args(const char ** l, size_t count) {
while (count--) append_copy(*l++);
m_data.push_back(nullptr);
}
Args(Args && o) = default;
Args(const Args &) = delete;
size_t size() const { return m_data.size() - 1; }
char ** data() { return m_data.data(); }
bool operator==(const Args & o) const {
if (size() != o.size()) return false;
for (size_t i = 0; i < size(); ++i)
if (strcmp(m_data[i], o.m_data[i]) != 0) return false;
return true;
}
};
Let's see how it works:
#include <iostream>
extern "C" int gsapi_init_with_args(void*, int argc, char** argv) {
for (int i = 0; i < argc; ++i)
std::cout << "arg " << i << "=" << argv[i] << std::endl;
return 0;
}
int main()
{
Args args1 { "foo", "bar", "baz" };
const char * args2i[] { "foo", "bar", "baz", nullptr };
Args args2 { (const char **)args2i };
const char * args3i[] { "foo", "bar", "baz" };
Args args3 { args3i };
const char * const args4i[] { "foo", "bar", "baz" };
Args args4 { args4i };
const char * args5i[] { "foo", "bar", "baz" };
Args args5 { args5i, sizeof(args5i)/sizeof(args5i[0]) };
assert(args1 == args2);
assert(args2 == args3);
assert(args3 == args4);
assert(args4 == args5);
gsapi_init_with_args(nullptr, args1.size(), args1.data());
}
Output:
arg 0=foo
arg 1=bar
arg 2=baz
Try to const_cast it:
gsapi_init_with_args(instance, argc, const_cast<char**>(argv));
Maybe it will help with fixing warning.
Inspired by n.m.'s C++14 version, here's a C++11 version. The trick is to use an evaluated empty lambda expression to generate a fresh type, so that each instantiation of W__ is unique.
template <typename T, int N> static char * W__(const char (&src)[N], T) {
static char storage[N];
strcpy(storage, src);
return storage;
}
#define W(x) W__(x, []{})
char * argv[] = {
W("foo"),
W("bar")
};
The static in front of W__'s return type means that W__ has internal linkage and won't bloat the object file with extra symbols. It has nothing to do with the static in front of storage, as the latter indicates the static storage duration for the local variable. The code below would be perfectly valid, but of course doing the wrong thing and having undefined behavior:
template <typename T, int N> static char * BAD(const char (&src)[N], T) {
char storage[N];
strcpy(storage, src);
return storage;
}
Since a lambda has to be evaluated, you can't simply make its type a template argument:
template<typename> void G();
G<decltype([]{})>(); // doesn't work
I have written some code to cast const char* to int by using constexpr and thus I can use a const char* as a template argument. Here is the code:
#include <iostream>
class conststr
{
public:
template<std::size_t N>
constexpr conststr(const char(&STR)[N])
:string(STR), size(N-1)
{}
constexpr conststr(const char* STR, std::size_t N)
:string(STR), size(N)
{}
constexpr char operator[](std::size_t n)
{
return n < size ? string[n] : 0;
}
constexpr std::size_t get_size()
{
return size;
}
constexpr const char* get_string()
{
return string;
}
//This method is related with Fowler–Noll–Vo hash function
constexpr unsigned hash(int n=0, unsigned h=2166136261)
{
return n == size ? h : hash(n+1,(h * 16777619) ^ (string[n]));
}
private:
const char* string;
std::size_t size;
};
// output function that requires a compile-time constant, for testing
template<int N> struct OUT
{
OUT() { std::cout << N << '\n'; }
};
int constexpr operator "" _const(const char* str, size_t sz)
{
return conststr(str,sz).hash();
}
int main()
{
OUT<"A dummy string"_const> out;
OUT<"A very long template parameter as a const char*"_const> out2;
}
In this example code, type of out is OUT<1494474505> and type of out2 is OUT<106227495>. Magic behind this code is conststr::hash() it is a constexpr recursion that uses FNV Hash function. And thus it creates an integral hash for const char* which is hopefully a unique one.
I have some questions about this method:
Is this a safe approach to use? Or can this approach be an evil in a specific use?
Can you write a better hash function that creates different integer for each string without being limited to a number of chars? (in my method, the length is long enough)
Can you write a code that implicitly casts const char* to int constexpr via conststr and thus we will not need aesthetically ugly (and also time consumer) _const user-defined string literal? For example OUT<"String"> will be legal (and cast "String" to integer).
Any help will be appreciated, thanks a lot.
Although your method is very interesting, it is not really a way to pass a string literal as a template argument. In fact, it is a generator of template argument based on string literal, which is not the same: you cannot retrieve string from hashed_string... It kinda defeats the whole interest of string literals in templates.
EDIT : the following was right when the hash used was the weighted sum of the letters, which is not the case after the edit of the OP.
You can also have problems with your hash function, as stated by mitchnull's answer. This may be another big problem with your method, the collisions. For example:
// Both outputs 3721
OUT<"0 silent"_const> out;
OUT<"7 listen"_const> out2;
As far as I know, you cannot pass a string literal in a template argument straightforwardly in the current standard. However, you can "fake" it. Here's what I use in general:
struct string_holder //
{ // All of this can be heavily optimized by
static const char* asString() // the compiler. It is also easy to generate
{ // with a macro.
return "Hello world!"; //
} //
}; //
Then, I pass the "fake string literal" via a type argument:
template<typename str>
struct out
{
out()
{
std::cout << str::asString() << "\n";
}
};
EDIT2: you said in the comments you used this to distinguish between several specializations of a class template. The method you showed is valid for that, but you can also use tags:
// tags
struct myTag {};
struct Long {};
struct Float {};
// class template
template<typename tag>
struct Integer
{
// ...
};
template<> struct Integer<Long> { /* ... */ };
// use
Integer<Long> ...; // those are 2
Integer<Float> ...; // different types
Here is the pattern that I am using for template const string parameters.
class F {
static constexpr const char conststr[]= "some const string";
TemplateObject<conststr> instance;
};
see :
https://stackoverflow.com/a/18031951/782168