7

I'm trying to pass a literal string as a template parameter in a C++14 project. Google told me that I can do as below:

struct Test {
    static const char teststr[];

    template<const char* str>
    void p() {std::cout << str;}
};

const char Test::teststr[] = "Hello world!";

int main() {
    Test t;
    t.p<Test::teststr>();
}

It did work.

However, if I use const char*, instead of const char []. It won't work.

struct Test {
    static const char* teststr;

    template<const char* str>
    void p() {std::cout << str;}
};

const char* Test::teststr = "Hello world!";

int main() {
    Test t;
    t.p<Test::teststr>();
}

Now it doesn't work. The compiler told me that 'Test::teststr' is not a valid template argument because 'Test::teststr' is a variable, not the address of a variable.

Well, I don't know what it meant.

Yves
  • 11,597
  • 17
  • 83
  • 180
  • Related: https://stackoverflow.com/questions/25946089/template-on-address-of-variable-with-static-storage – Peter May 26 '22 at 09:54

3 Answers3

6

The error message from the compiler is clear enough:

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

So you need:

#include <iostream>

struct Test {
    static const char* teststr;

    template<const char **str>
    void p() {std::cout << *str;}
};

const char* Test::teststr = "Hello world!";

int main() {
    Test t;
    t.p <&Test::teststr>();
}

And then it works - the point being that [the contents of] a variable is not a compile-time constant, whereas the address of a variable (if it's a static or global variable) is.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Interesting: If you wanted to use it with an array, as in the OP's use case from google, you'd have to introduce an in-between static helper pointer variable, initialize it with the array's first element's address and and pass *its* address as template param: `const char Test::teststr[] = "Hello world!"; const char *testStrPtr = Test::teststr; /* or ... = &Test::teststr[0]; */`and in main: `t.p <&Test::teststrPtr>();`. – Peter - Reinstate Monica May 26 '22 at 10:05
  • @Peter-ReinstateMonica [That's not the only way](https://wandbox.org/permlink/BtRU3T1IogQODxdp) – Paul Sanders May 26 '22 at 11:13
  • But that calls the [`template` function](https://wandbox.org/permlink/ubwHi6pSVltJnSAF). – Peter - Reinstate Monica May 26 '22 at 11:46
  • @Peter-ReinstateMonica Well yes, that was my intention. – Paul Sanders May 26 '22 at 12:09
  • I suppose the OP would like to have a single function usable with both variants (as would be the case with non-template arguments where arrays are adjusted to pointers when they appear as arguments). – Peter - Reinstate Monica May 26 '22 at 13:35
  • @Peter-ReinstateMonica Quite possibly. – Paul Sanders May 26 '22 at 13:50
  • @Peter-ReinstateMonica [This](https://wandbox.org/permlink/wxfKRnGtxl46AeA2) seems to work. – Paul Sanders May 26 '22 at 17:41
5

This is just according to the rules of c++:

Template non-type arguments.
For pointers to objects, the template arguments have to designate the address of a complete object with static storage duration and a linkage (either internal or external)

https://en.cppreference.com/w/cpp/language/template_parameters

A global array of characters has linkage while a string literal doesn’t.
In C++20 this has been changed and you can use string literals as template parameters.

Daniel
  • 30,896
  • 18
  • 85
  • 139
  • `const char*` isn't a string literal, it is a pointer to const char. Thus, your answer is incorrect because you said: *"A global array of characters has linkage while a string literal doesn’t."* but this doesn't apply to `teststr` as it(`teststr`) is a `const char*` and not a string literal. – Jason May 26 '22 at 12:40
2

The problem is that in case 2 of your example the static data member teststr is a pointer type that has the address of a string literal which is not allowed by the standard as can be seen from the quoted statement below ,

From non-type template parameter's documentation:

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.1) a subobject (1.8),

  • (1.2) a temporary object (12.2),

  • (1.3) a string literal (2.13.5),

Thus, in case 2 of your example teststr cannot be used as it is a pointer type that has the address of the string literal "Hello world!".


Jason
  • 36,170
  • 5
  • 26
  • 60
  • And I suppose the reason to forbid string literals is that they may or may not be the same objects, and the compiler wouldn't know how to compare for equality. (Two identical literals may trigger two different instantiations.) – Peter - Reinstate Monica May 26 '22 at 19:45
  • The accepted answer isn't incorrect it just presents an arguably weird solution while not providing the reason why in full. Your answer isn't full either because even if that restriction wasn't present the OP's code would still be incorrect because they have non-constant pointer. So your quote had not chance to be applied yet. – ixSci May 27 '22 at 09:29
  • @ixSci Even if the `teststr` was `const`/`constexpr` the program wouldn't work. My point is that OP asked: *"I don't know what it meant."* and the accepted answer only repeated the question instead of answering the question. Moreover the reason given there isn't correct either. Because even if you make `testptr` a `constexpr` the program will still fail and the reason is as specified in my answer. That answer ignores the underlying issue which isn't just that `testptr` is not a compile time constant but also that the standard prohibits such usage even if it was a compile time constant. – Jason May 27 '22 at 09:42
  • Your concerns are valid but they don't make the answer wrong. They made illegitimate code legitimate by working around the underlying issue w/o mentioning it entirely! So their answer is technically correct, although the OP hardly learned anything from it. On the other hand you are talking about string literals while the OP didn't get to them yet. So the OP had to add `const` (or `constexpr`) to the pointer to be stricken by the quote you cited. So yes, the underlying issue is that literals can't be used literally but the immediate issue is different. – ixSci May 27 '22 at 09:51