Template Tricks with Const Char* as a Non-Type Parameter

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

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.

C++ non-type template parameter const char*

The array a is a constant array of character, it's fully initialized at the time of compilation and also gets a know memory address by the compiler which is why it can decay to a pointer in a template.

But p is a pointer to a constant array of characters, but the pointer itself is not a compile-time constant, and can be changed to point to other strings, and it's is not initialized at time of compilation but either on time of linking (which happens after compilation) or when the program is loaded into memory. The address of p is known at time of compilation, but not the address of the string literal p points to.


To expand on the reason the address of the string literal is not known at time of compilation, it's because it's put in a special read-only segment by the compilers code-generator, and that read-only segment is then combined with the read-only segments from the other translation units when linking. This is why the final address of the string literal can't be known until the time of linking (at earliest).

Why can an array of char be a template parameter but a const char* can't

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.

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.

Template non-type parameter deduction

What you want can only be done by (ab)using type deduction for integer deduction. Observe:

template<int x>
struct integer_value {};

template<int x>
void test(integer_value<x> val)
{
//x can be used here.
}

Of course, you must invoke this with test(integer_value<4>{}) or something similar.

How to make a non-type template base class with non-template derived class

Is it possible to have a template base class and a non-template derived class?

Yes, see AAA, BBB, CCC in the example below.

If so, what if the base class is a non-type template?

See DDD, EEE, FFF in the example below.

In C++20 you can have some kind of string-literal template arguments.
This page gives a concise example about their usage.

I'm not certain about your intention, and what is supposed to be constexpr or dynamic in your use case, but here is an attempt.
Some properties of the string-literals are checked at compile time thanks to static_assert().

/**
g++ -std=c++20 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#include <iostream>
#include <algorithm> // std::copy_n()

template<typename T> class AAA { public: T member; };
class BBB: public AAA<int> { };
class CCC: public AAA<double> { };

template<int N> class DDD { public: static constexpr int value=N; };
class EEE: public DDD<100> { };
class FFF: public DDD<1000> { };

template<size_t N>
struct StringLiteral
{
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); }
char value[N];
};

constexpr bool someFunction(const char *n) { return n[0]<n[1]; }

template<StringLiteral name>
class Base
{
public:
static constexpr const char* base_name=name.value;
constexpr Base() { static_assert(someFunction(base_name)); }
};

class Derived1: public Base<"def">
{
public:
constexpr Derived1(int i): Base{}, i_{i} { }
void f() { i_+=1; std::cout << base_name << ' ' << i_ << '\n'; }
private:
int i_;
};

class Derived2: public Base<"ghi">
{
public:
constexpr Derived2(int i): Base{}, i_{i} { }
void f() { i_+=2; std::cout << base_name << ' ' << i_ << '\n'; }
private:
int i_;
};

/*
class BadDerived3: public Base<"jhi"> // static_assert() fails
{
public:
constexpr BadDerived3(int i): Base{}, i_{i} { }
void f() { i_+=3; std::cout << base_name << ' ' << i_ << '\n'; }
private:
int i_;
};
*/

int
main()
{
AAA<char> aaa{'A'};
BBB bbb{123};
CCC ccc{45.67};
std::cout << aaa.member << ' '
<< bbb.member << ' '
<< ccc.member << '\n'; // displays A 123 45.67
DDD<10> ddd{};
EEE eee{};
FFF fff{};
std::cout << ddd.value << ' '
<< eee.value << ' '
<< fff.value << '\n'; // displays 10 100 1000
// Base<"zbc"> bad_b; // static_assert() fails
Base<"abc"> b;
Derived1 d1{10};
Derived2 d2{20};
d1.f(); // displays def 11
d2.f(); // displays ghi 22
return 0;
}

EDIT

After a comment about C++20 support, here is a modified version that works with C++11.
We cannot provide our own type as template parameter, but references are allowed.
The trick is to create a constexpr string-like data with static storage and reference it as a template parameter.
This is not as comfortable as the C++20 version, but it is still usable since each derived class only needs its constexpr name just before.
All of this is shamelessly inspired from the various answers on this page.

/**
g++ -std=c++11 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#include <iostream>

struct constexpr_str
{
const char* const data;
const std::size_t size;
template<std::size_t N>
constexpr constexpr_str(const char(&d)[N]) : data{d}, size{N-1} { }
};

constexpr bool someFunction(constexpr_str n) { return n.data[0]<n.data[1]; }

template<const constexpr_str &name>
class Base
{
public:
static constexpr constexpr_str base_name{name};
constexpr Base() { static_assert(someFunction(base_name), "bad_name"); }
};

constexpr auto derived1_name=constexpr_str{"def"};
class Derived1: public Base<derived1_name>
{
public:
constexpr Derived1(int i): Base{}, i_{i} { }
void f() { i_+=1; std::cout << base_name.data << ' ' << i_ << '\n'; }
private:
int i_;
};

constexpr auto derived2_name=constexpr_str{"ghi"};
class Derived2: public Base<derived2_name>
{
public:
constexpr Derived2(int i): Base{}, i_{i} { }
void f() { i_+=2; std::cout << base_name.data << ' ' << i_ << '\n'; }
private:
int i_;
};

/*
constexpr auto derived3_name=constexpr_str{"jhi"};
class BadDerived3: public Base<derived3_name> // static_assert() fails
{
public:
constexpr BadDerived3(int i): Base{}, i_{i} { }
void f() { i_+=3; std::cout << base_name.data << ' ' << i_ << '\n'; }
private:
int i_;
};
*/

constexpr auto bad_base_name=constexpr_str{"zbc"};
constexpr auto base_name=constexpr_str{"abc"};

int
main()
{
constexpr auto txt=constexpr_str{"ABCD"};
static_assert(txt.size==4, "incorrect size");
static_assert(txt.data[1]=='B', "incorrect char");
// Base<bad_base_name> bad_b; // static_assert() fails
Base<base_name> b;
Derived1 d1{10};
Derived2 d2{20};
d1.f(); // displays def 11
d2.f(); // displays ghi 22
return 0;
}


Related Topics



Leave a reply



Submit