Template tricks with const char* as a non-type parameter

10,889

1. Short answer: It works irrespective of it being declared constexpr, because you're defining an object with static storage duration (that is not a string literal - it stores a copy of the contents of one), and its address is a constant expression. Regarding linkage, str2 has internal linkage, but that's fine - its address can be used as a non-type template argument.

Long answer:

In C++11 and 14, [14.3.2p1] says the following:

A template-argument for a non-type, non-template template-parameter shall be one of:
[...]

  • 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;

[...]

So, you can use the address of an object with static storage duration, but the object has to be identified by a name with linkage (internal or external), and the way you're expressing that address is restricted. (String literals are not names and don't have linkage.)

In short, even char str1[] = "Test 1"; works. static char str1[] = "Test 1"; is fine as well; GCC 5.1.0 rejects it, but I think that's a bug; Clang 3.6.0 accepts it.


About str2's linkage, C++11 and 14 [3.5p3] says:

A name having namespace scope (3.3.6) has internal linkage if it is the name of
[...]

  • a non-volatile variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage;

[...]

N4431 has changed that slightly, as a result of DR 1686, to:

  • a variable of non-volatile const-qualified type that is neither explicitly declared extern nor previously declared to have external linkage;

reflecting the fact that constexpr implies const-qualification for objects.


2. Short answer: For C++11 and 14, see above; for draft C++1z, str3 is not a constant expression, as the pointer itself is not constexpr, and it's also the address of a string literal. str4 is constant, but still an address of a string literal.

Long answer:

In the current working draft, N4431, the constraints on non-type template arguments have been relaxed. [14.3.2p1] now says:

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

And those are all the restrictions. The converted constant expression part is pretty important; the full definition is long, but one part relevant to our case is that the address of an object with static storage duration is such an expression.

Also relevant is that, according to [5.20p2.7], an lvalue-to-rvalue conversion applied to

a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable sub-object of such an object

also satisfies the conditions for being a constant expression. This allows us to use some constexpr pointer variables as non-type template arguments. (Note that simply declaring a variable const is not enough, as it can be initialized with a non-constant expression.)

So, something like constexpr const char* str3 = str1; is fine. It's accepted by Clang 3.6.0 in C++1z mode (and rejected in C++14 mode); GCC 5.1.0 still rejects it - it looks like it hasn't implemented the updated rules yet.


Still, what's wrong with string literals? Here's the problem (N4431 [2.13.5p16]):

Evaluating a string-literal results in a string literal object with static storage duration, initialized from the given characters as specified above. Whether all string literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified.

An implementation is allowed to do lots of things with string literals: mix, match, make them overlap (entirely or partially), make 7 copies from the same translation unit - whatever. That makes the address of a string literal unusable as a non-type template argument.

Share:
10,889

Related videos on Youtube

vsoftco
Author by

vsoftco

Quantum Information and Computation senior research scientist, cybersecurity professional, C++ programmer. I am the author of Quantum++, a high-performance C++11 library for simulation of universal quantum computing, and a core member of Open Quantum Safe, an open-source project that aims to support the development and prototyping of quantum-resistant cryptography.

Updated on September 16, 2022

Comments

  • vsoftco
    vsoftco over 1 year

    I am very well aware that passing directly a const char* as a template non-type parameter is erroneous, since two identical string literals defined in two different translation units may have different addresses (although most of the time the compilers use the same address). There is a trick one may use, see code below:

    #include <iostream>
    
    template<const char* msg>
    void display()
    {
        std::cout << msg << std::endl;
    }
    
    // need to have external linkage 
    // so that there are no multiple definitions
    extern const char str1[] = "Test 1"; // (1)
    
    // Why is constexpr enough? Does it have external linkage?
    constexpr char str2[] = "Test 2";    // (2)
    
    // Why doesn't this work? 
    extern const char* str3 = "Test 3";  // (3) doesn't work
    
    // using C_PTR_CHAR = const char* const;   // (4) doesn't work either
    extern constexpr C_PTR_CHAR str4 = "Test 4"; 
    
    int main()
    {
        display<str1>();    // (1')
        display<str2>();    // (2')
        // display<str3>(); // (3') doesn't compile 
        //display<str4>();  // (4') doesn't compile
    }
    

    Basically in (1) we declare and define an array with external linkage, which can then be used as a template parameter in (1'). I understand this very well. However, I don't understand:

    1. Why does the constexpr version (2) work? Does constexpr have external linkage? If not, then defining the same string literal in a different translation unit may lead to duplicate template instantiation.

    2. Why don't (3) and (4) work? It seems perfectly reasonable for me, but the compiler doesn't believe so:

      error: 'str3' is not a valid template argument because 'str3' is a variable, not the address of a variable

    • πάντα ῥεῖ
      πάντα ῥεῖ almost 9 years
      I've see a similar question recently, that asked about why a extern declaration cannot be used for compile time value deduction. It's probably because the linker actually handles extern, and the compiler can't use that info when the code is compiled.
    • vsoftco
      vsoftco almost 9 years
      @πάνταῥεῖ What's odd is that in (1) I use an extern definition, which works.
    • Filip Roséen - refp
      Filip Roséen - refp almost 9 years
      what makes (3) different from the other two is that you'd like the address contained "inside" the variable, and not the address of the variable itself. I'm currently on my phone, and as such I might look back at this question at a later time to provide an answer.
    • πάντα ῥεῖ
      πάντα ῥεῖ almost 9 years
      Yes, but there the size can be deduced from the literal, while it can't be deduced for a plain const char* pointer.
    • jaggedSpire
      jaggedSpire almost 9 years
      @FilipRoséen-refp The same thing happens as with (3) when you use constexpr instead. I'm assuming the same problem is happening there?
    • vsoftco
      vsoftco almost 9 years
      @jaggedSpire I've even tried extern constexpr CPTR str4 = "Something";, where using CPTR = const char*; Still same thing.
    • Filip Roséen - refp
      Filip Roséen - refp almost 9 years
      @jaggedSpire Yes, and this is due to the wording related to non-type template parameters and pointer types - addressed in [temp.arg.nontype]p1 if I recall correctly. (the fact that I can point to a specific section of the standard without looking at it is.. well, not sure if it's a curse or a blessing)
    • Filip Roséen - refp
      Filip Roséen - refp almost 9 years
      if a non-type template parameter is of pointer type, the passed constant-expression must be "&name-of-object”, the ampersand can be left out for objects of array type, and function types, otherwise it is required. (there is more to the matter, but see [temp.arg.nontype] for more info)
    • molbdnilo
      molbdnilo almost 9 years
      Note that the actual template parameter for the first two are pointers to the first element of the arrays (i.e. they're using the addresses of the variables). The other two use the values of the variables, not their addresses.
    • R Sahu
      R Sahu almost 9 years
      I find it strange that using char str1[] = "Test 1"; works but using const char str1[] = "Test 1"; does not work.
    • bogdan
      bogdan almost 9 years
      @RSahu I think that's a bug in GCC. Both work fine in Clang 3.6.0 in C++14 mode.
    • dyp
      dyp almost 9 years
      In C++03, an address used as a non-type template argument had to refer to an object (or function) with external linkage. That restriction was removed in C++11, similarly to using local types as type template arguments.
    • PaperBirdMaster
      PaperBirdMaster almost 9 years
      I made a similar question time ago, hope it helps.
    • bogdan
      bogdan almost 9 years
      @PaperBirdMaster Note that some sentences in the accepted answer to that question are in the past tense for a good reason (they refer to how things used to be, see dyp's comment above), so the two Q/As could be seen as complementing each other. I think it's good that the two questions are now linked.
  • vsoftco
    vsoftco almost 9 years
    I can even make the pointer both const and constexpr, see the slightly updated edit, still same issue. The fact that it is the address of a string literal shouldn't be an issue provided it has external linkage, so the address is the same over all translation units. But the standard says no, I see... I guess my question is more like "why does the standard say no?" Thanks for the quote anyway!
  • jaggedSpire
    jaggedSpire almost 9 years
    So the first two examples work because they are technically character arrays constructed from string literals, and not string literals themselves?
  • bogdan
    bogdan almost 9 years
    @jaggedSpire Yes, that is definitely the case.
  • rici
    rici almost 9 years
    @jagged: They are arrays and like any object with external linkage they have well-defined addresses, unlike the string literals they are copied from. There is nothing "technical" about it.