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 constexpr is 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:
Why the constexpr
version (2) works? Do constexpr
have external linkage? If not, then defining the same string literal in a different translation unit may lead with duplicate template instantiation.
Why (3) and (4) don't 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
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.