String Literals Not Allowed as Non Type Template Parameters

String literals not allowed as non type template parameters

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.

Template Non-Type argument, C++11, restriction for string literals

The closest that string literals come to being allowed is, from your question:

a constant expression (5.19) that designates the address of an object with static storage duration and external or internal linkage

String literals have neither external linkage nor internal linkage, so they aren't allowed.

If you have multiple translation units, each containing a definition of const char arr[5]; with internal linkage, then those are all distinct objects, with distinct addresses, but within a single translation unit, arr == arr, always. Implementations figured out how to make that work for template arguments.

If you have multiple translation units, each containing "1234", then those are not guaranteed to have distinct addresses. Yet even in a single translation unit, they are not guaranteed to have the same address.

If "1234" != "1234", then referring to a template S<"1234"> makes no sense: you'd be referring to a different template instantiation each time.

If "1234" == "1234", then it gets complicated for implementations to make sure that S<"1234"> is the same type in each translation unit.

Passing a string literal as a type argument to a class template

Sorry, C++ does not currently support the use of string literals (or real literals) as template parameters.

But re-reading your question, is that what you are asking? You cannot say:

foo <"bar"> x;

but you can say

template <typename T>
struct foo {
foo( T t ) {}
};

foo <const char *> f( "bar" );

Non-type template parameters

The reason you can't do this is because non-constant expressions can't be parsed and substituted during compile-time. They could change during runtime, which would require the generation of a new template during runtime, which isn't possible because templates are a compile-time concept.

Here's what the standard allows for non-type template parameters (14.1 [temp.param] p4):

A non-type template-parameter shall have one of the following (optionally cv-qualified) types:

  • integral or enumeration type,
  • pointer to object or pointer to function,
  • lvalue reference to object or lvalue reference to function,
  • pointer to member,
  • std::nullptr_t.

pointer non-type template parameter

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.

std::source_location as non type template parameter

std::source_location is specified as:

  struct source_location {
// ...

private:
uint_least32_t line_; // exposition only
uint_least32_t column_; // exposition only
const char* file_name_; // exposition only
const char* function_name_; // exposition only
};

And the rules for the kinds of types that can be used as non-template template parameters require that a type be structural, which means, from [temp.param]/7, emphasis mine:

A structural type is one of the following:

  • a scalar type, or
  • an lvalue reference type, or
  • a literal class type with the following properties:
    • all base classes and non-static data members are public and non-mutable and
    • the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.

source_location does not have all of its non-static data members public, so it is not structural, so it is not usable as a non-type template parameter.


This part:

template <auto src_loc = std::experimental::source_location::current().file_name()>

does not work because of [temp.arg.nontype]/3:

For a non-type template-parameter of reference or pointer type, or for each non-static data member of reference or pointer type in a non-type template-parameter of class type or subobject thereof, the reference or pointer value shall not refer to or be the address of (respectively):

  • [...],
  • a string literal object ([lex.string]),
  • ...

But what you can do is create your own type, that is structural, that is constructible from source_location. It's just that the strings can't be char const*, they have to own the data. If you look in the examples in P0732, we can build up:

template <typename Char, size_t N>
struct basic_fixed_string { ... };

template <basic_fixed_string S> struct A {};

using T = A<"hello">;

That might be awkward to deal with in this case, so you could also just pick some reasonable max size and go with that.

What does the C++20 standard say about usage of subojects as template non-type arguments?

The wording changed as part of P1907R1, which was adopted as part of C++20. Note that the first draft you cited - N4835 - predates this adoption (that draft was published Oct 2019, and this paper was adopted the following month at the Belfast meeting in Nov 2019). The closest draft to C++20 is N4861, which you can also conveniently view in html form.

As a result, the following:

template<int* p> class X {};
int a[10];
struct S
{
int m;
static int s;
} s;

X<&a[2]> x3;
X<&s.m> x4;

is a valid C++20 program, since neither a[2] nor s.m are subobjects of any of: a temporary, a string literal, the result of a typeid expression, or __func__.


The cppreference example has already been updated to reflect this, where the comments now read:

X<&a[2]> x3;  // error (until C++20): address of array element
X<&s.m> x4; // error (until C++20): address of non-static member

Trying to pass string literals as template arguments

re: your OP: I'd like to know why a couple of them failed.

The comment by @NatanReed is correct:

  • Your first snippet fails because Get needs a TYPE and is given an object.
  • Your second snippet fails because it is illegal to define a template argument as reference to an object.

    • until C++2003, that is. Then reference to an object became legal.

Template arguments must be constants from a limited set of types.

  • See: ISO/IEC 14882-2003 §14.1: Template parameters
  • See: ISO/IEC 14882-2003 §14.3.2: Template non-type arguments

And even then, the String constexpr str = "hello"; must have external linkage. So putting it on the stack inside of main() is not going to work.

Give this a try:

#include <iostream>
#include <string>

using namespace std;

struct String {
char const *m_sz;

constexpr String(char const *a_sz)
:
m_sz(a_sz) {}
};

template<String const &_rstr>
string const Get() {
return _rstr.m_sz;
}

extern String constexpr globally_visible_str = "hello";
int main() {
cout << Get<globally_visible_str>() << endl;
return 0;
}


Related Topics



Leave a reply



Submit