Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pointer non-type template parameter

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?
like image 544
vsoftco Avatar asked Mar 12 '15 03:03

vsoftco


People also ask

What are non-type parameters for templates?

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.

What is non-type parameters in C++?

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.

What is template parameter in c++?

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.

Can a template be a template parameter?

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.


2 Answers

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.
like image 75
Quuxplusone Avatar answered Sep 28 '22 09:09

Quuxplusone


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.

like image 21
user657267 Avatar answered Sep 28 '22 09:09

user657267