The following quote is from C++ Templates by Addison Wesley. Could someone please help me understand in plain English/layman\'s terms its gist?
It means you can't do this...
#include <iostream>
template <const char* P>
void f() { std::cout << P << '\n'; }
int main()
{
f<"hello there">();
}
...because "hello there"
isn't 100% guaranteed to resolve to a single integral value that can be used to instantiate the template once (though most good linkers will attempt to fold all usages across linked objects and produce a new object with a single copy of the string).
You can, however, use extern character arrays/pointers:
...
extern const char p[];
const char p[] = "hello";
...
f<p>();
...
Your compiler ultimately operates on things called translation units, informally called source files. Within these translation units, you identify different entities: objects, functions, etc. The linkers job is to connect these units together, and part of that process is merging identities.
Identifiers have linkage†: internal linkage means that the entity named in that translation unit is only visible to that translation unit, while external linkage means that the entity is visible to other units.
When an entity is marked static
, it is given internal linkage. So given these two translation units:
// a.cpp
static void foo() { /* in a */ }
// b.cpp
static void foo() { /* in a */ }
Each of those foo
's refer to an entity (a function in this case) that is only visible to their respective translation units; that is, each translation unit has its own foo
.
Here's the catch, then: string literals are the same type as static const char[..]
. That is:
// str.cpp
#include <iostream>
// this code:
void bar()
{
std::cout << "abc" << std::endl;
}
// is conceptually equivalent to:
static const char[4] __literal0 = {'a', 'b', 'c', 0};
void bar()
{
std::cout << __literal0 << std::endl;
}
And as you can see, the literal's value is internal to that translation unit. So if you use "abc"
in multiple translation units, for example, they all end up being different entities.‡
Overall, that means this is conceptually meaningless:
template <const char* String>
struct baz {};
typedef baz<"abc"> incoherent;
Because "abc"
is different for each translation unit. Each translation unit would be given a different class because each "abc"
is a different entity, even though they provided the "same" argument.
On the language level, this is imposed by saying that template non-type parameters can be pointers to entities with external linkage; that is, things that do refer to the same entity across translation units.
So this is fine:
// good.hpp
extern const char* my_string;
// good.cpp
const char* my_string = "any string";
// anything.cpp
typedef baz<my_string> coherent; // okay; all instantiations use the same entity
†Not all identifiers have linkage; some have none, such as function parameters.
‡ An optimizing compiler will store identical literals at the same address, to save space; but that's a quality of implementation detail, not a guarantee.
As mentioned in other answers, a string literal cannot be used as a template argument. There is, however, a workaround which has a similar effect, but the "string" is limited to four characters. This is due to multi-character constants which, as discussed in the link, are probably rather unportable, but worked for my debug purposes.
template<int32_t nFourCharName>
class NamedClass
{
std::string GetName(void) const
{
// Evil code to extract the four-character name:
const char cNamePart1 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*3) & 0xFF);
const char cNamePart2 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*2) & 0xFF);
const char cNamePart3 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*1) & 0xFF);
const char cNamePart4 = static_cast<char>(static_cast<uint32_t>(nFourCharName ) & 0xFF);
std::ostringstream ossName;
ossName << cNamePart1 << cNamePart2 << cNamePart3 << cNamePart4;
return ossName.str();
}
};
Can be used with:
NamedClass<'Greg'> greg;
NamedClass<'Fred'> fred;
std::cout << greg.GetName() << std::endl; // "Greg"
std::cout << fred.GetName() << std::endl; // "Fred"
As I said, this is a workaround. I don't pretend this is good, clean, portable code, but others may find it useful. Another workaround could involve multiple char template arguments, as in this answer.
Obviously, string literals like "foobar" are not like other literal built-in types (like int or float). They need to have an address (const char*). The address is really the constant value that the compiler substitutes in place of where the literal appears. That address points to somewhere, fixed at compile-time, in the program's memory.
It has to be of internal linkage because of that. Internal linkage just means that cannot be linked across translation units (compiled cpp files). The compiler could try to do this, but is not required to. In other words, internal linkage means that if you took the address of two identical literal strings (i.e. the value of the const char* they translate to) in different cpp files, they wouldn't be the same, in general.
You can't use them as template parameters because they would require a strcmp() to check that they are the same. If you used the ==, you would just be comparing the addresses, which wouldn't be the same when template are instantiated with the same literal string in different translation units.
Other simpler built-in types, as literals, are also internal linkage (they don't have an identifier and can't be linked together from different translation units). However, their comparison is trivial, as it is by value. So they can be used for templates.
Idea of c++ standard only allowing certain type of parameters to the templates is that parameter should be constant and known at compile time in order to generate "specialized class" code.
For this specific case: When you create string literal their address is unknown until linking time (linking happens after compilation) because two string literals across different translation units are two different objects (as explained brilliantly by accepted answer). When compilation happens we don't know which string literal's address to use to generate the specialized class code from template class.