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
orconstexpr
and neither explicitly declaredextern
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
withconstexpr
, 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
How to Define a Move Constructor
Calling Function Template Specialization Using C Calling Conventions
C++11 Constexpr Flatten List of Std::Array into Array
Is There a Reason Why Not to Use Link-Time Optimization (Lto)
Vector: Initialization or Reserve
C++, Function Pointer to the Template Function Pointer
Cmake Simple Config File Example
Qt 4.X: How to Implement Drag-And-Drop Onto the Desktop or into a Folder
When Should We Use Sizeof with and Without Parentheses
There Are No Arguments That Depend on a Template Parameter
How to Install Tensorflow on Windows
Advice on Mocking System Calls
Const Pointer Assign to a Pointer
What's the Point of Std::Unique_Ptr::Get
Win32 - Get Main Wnd Handle of Application