I don't really understand why the code below does not compile:
template<const char*>
struct Foo{};
constexpr const char s1[] = "test1";
constexpr const char* const s2 = "test2";
int main()
{
Foo<s1> foo1; // ok
// Foo<s2> foo2; // doesn't compile
}
Uncommenting the last line in main()
makes g++ and clang++ emit the errors
error: 's2' is not a valid template argument because 's2' is a variable, not the address of a variable
and
error: non-type template argument for template parameter of pointer type 'const char *' must have its address taken
respectively.
My questions are:
s1
instantiation OK and s2
not?A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type.
A non-type template argument provided within a template argument list is an expression whose value can be determined at compile time. Such arguments must be constant expressions, addresses of functions or objects with external linkage, or addresses of static class members.
A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
Templates can be template parameters. In this case, they are called template parameters. The container adaptors std::stack, std::queue, and std::priority_queue use per default a std::deque to hold their arguments, but you can use a different container.
In a comment above, vsoftco adds:
seems extremely weird, afaik string literals are not temporaries but are stored for the whole duration of the program, so their address is for sure a compile time constant (or at least that's what I believe)
That's true. However, the standard doesn't specify whether string literals have unique addresses.
Some linkers merge or deduplicate string literals. I have worked on systems where "ello" == "hello"+1
actually evaluates to true
. Other linkers are so dumb that "hello"
in foo.cc has a different address from "hello"
in bar.cc. Heck, some tiny C compilers are so dumb that "hello"
can have two different addresses within the same translation unit!
For such a dumb linker (or compiler), should Foo<"hello">
cause one instantiation or two? That is...
const char *sa = "hello world";
const char *sb = "hello world";
assert(sa != sb); // this assertion is permitted to succeed
template<char*> struct F {};
F<"hello world"> fa;
F<"hello world"> fb;
assert(!is_same<decltype(fa), decltype(fb)>::value);
// should we permit this assertion to succeed also?
The Committee admirably refused to open that can of worms, by simply prohibiting the construct.
Now, it's conceivable (to me, at the moment) that sometime in the future the Committee could mandate that all string literals be deduplicated by the same mechanism that implementations currently use for inline
and template
functions. That is, we can imagine a source-level transformation that turns
const char *sc = "yoo hoo";
into
inline auto& __stringlit_yoo_x20hoo() {
static const char x[] = "yoo hoo";
return x;
}
const char *sc = __stringlit_yoo_x20hoo();
Then there would be only a single instance of __stringlit_yoo_x20hoo
(and only a single instance of that function's static array x
) anywhere in the program, so the meaning of F<"yoo hoo">
would be unambiguous. The implementation would have to name-mangle the thing unambiguously as well, but that's a simple problem once you've already committed to name-mangling things like F<1+1>
and F<FruitType,ORANGE>
(which C++ compilers have been doing forever).
...But then you would still have problems with those extremely smart linkers (like the one I worked on) that allow
assert("hello" == "hello\0world"); // this assertion is permitted to succeed
assert(!is_same_v< F<"hello">, F<"hello\0world"> >);
// should we permit this assertion to succeed also?
// Surely this way lies madness.
For 1.:
From [temp.arg.nontype]
1 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 or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):
[...]
(1.3) — a string literal (2.13.5),
s2
holds the address of a string literal, and so cannot be used as the parameter here. s1
on the other hand is an array of char
that has been initialized with a string literal, but the value of s1
(when converted to const char*
) doesn't point to the string literal used in the initialization.
For 2.:
Function pointers perhaps? Still I can't say I've ever used a pointer as a non-type parameter.
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