It has happened to many people, and it happened to me. I got stuck playing with compile time strings in C++.
I decided to take the apparently unusable approach: using template <char...>
classes.
This is what I came up with, it is very common, nothing special, and also it does not work.
template <char... chars> class string
{
public:
static constexpr const char value[] = {chars...};
constexpr string()
{
}
constexpr operator decltype(value) & () const
{
return value;
}
};
template <char... chars> constexpr const char string <chars...> :: value[];
My idea was making a string
instance constexpr
constructible and exposing some kind of constexpr casting so that it would provide its content.
Now, if I do
static constexpr const char x[] = "ciao";
template <const char * str> void print()
{
std :: cout << str << std :: endl;
}
print <x> ();
This works and says ciao
. I get ciao
also if I do
std :: cout << string <'c', 'i', 'a', 'o'> {} << std :: endl;
or
print <string <'c', 'i', 'a', 'o', '\0'> :: value> ();
But when I do
print <string <'c', 'i', 'a', 'o', '\0'> {}> ();
I get: No matching function for call to print
.
I am definitely missing something. Is it unfeasible to do what I am trying to do? Making an instance do a constexpr cast to somehow return value
? If that worked, I would be able to easily make operators and string manipulation at compile time, the "only" downside being the ultra-boring 'i', 'n', 'i', 't', 'i', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n'
.
I did another experiment that works perfectly.
template <char... chars> class string
{
public:
constexpr string()
{
}
constexpr operator size_t () const
{
return sizeof...(chars);
}
};
template <size_t length> void print()
{
std :: cout << length << std :: endl;
}
print <string <'c', 'i', 'a', 'o'> {}> ();
And it prints out a pretty 4
.
In C++14, the restriction on template arguments to a template non-type parameter is:
A template-argument for a non-type, non-template template-parameter shall be one of:
* for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter; or
* the name of a non-type template-parameter; or
* a constant expression (5.19) that designates the address of a complete object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as&
id-expression, where the id-expression is the name of an object or function, except that the&
may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or
* a constant expression that evaluates to a null pointer value (4.10); or
* a constant expression that evaluates to a null member pointer value (4.11); or
* a pointer to member expressed as described in 5.3.1; or
* a constant expression of typestd::nullptr_t
.
In your example, string<'c', 'i', 'a', 'o', '\0'>{}
is none of those. Note that the first bullet is restricted to integral or enumeration type, and const char*
is neither of those (the exception for integral types is what allows your "Further experiments" example to compile). None of the other bullets apply. So a conforming C++14 should reject your code.
The workaround is to simply pass the underlying character array directly:
print<string<'c', 'i', 'a', 'o', '\0'>::value>();
Or to take the type itself as the template type argument, and print ::value
within the function:
template <class T>
void print() {
std::cout << T::value << std::endl;
}
print<string<'c', 'i', 'a', 'o', '\0'>>();
Or...
You'll be happy to know that in C++17, things get better. See N4198, with the motivating example:
template<int *p> struct A {};
int n;
A<&n> a; // ok
constexpr int *p() { return &n; }
A<p()> b; // error
The paper proposed fewer restrictions on template non-type arguments, which would make the above example well-formed. The new wording reads:
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter. For a non-type template-parameter of reference type or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):
* a subobject (1.8),
* a temporary object (12.2),
* a string literal (2.14.5),
* the result of atypeid
expression (5.2.8), or * a predefined__func__
variable (8.4.1).
That's it. Everything else is allowed.
Since string<'c', 'i', 'a', 'o', '\0'>{}
converts to a pointer that is none of those things, the example becomes well-formed. Clang 3.8 will compile your example in c++1z mode (correctly) but not in c++14 mode (correctly). GCC does not implement this yet. Just give them time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With