18
votes

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:

  1. Why is s1 instantiation OK and s2 not?
  2. Is there any sane situation where such pointer non-type template parameter is of any use?
3

3 Answers

7
votes

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.

7
votes

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.
6
votes

There's a recent change in the relevant standard text, but the code is acceptable in neither version of the standard.

N4140 [temp.arg.nontype]/p1:

1 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 type std::nullptr_t.

N4296 [temp.arg.nontype]/p1:

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):

  • a subobject (1.8),
  • a temporary object (12.2),
  • a string literal (2.13.5),
  • the result of a typeid expression (5.2.8), or
  • a predefined __func__ variable (8.4.1).

The N4140 version is the one currently implemented by compilers. The N4296 version is somewhat more relaxed, but in neither case is the address of a string literal an acceptable template argument.

Presumably part of the reason for this is that template arguments must be mangled, and mangling a string literal in a sane way that would work across multiple translation units would be very difficult, if not impossible.