15

When using templates in c++ I sometime need to pass strings as value template parameters.

I found quite difficult to understand why certain parameters are allowed and other are not.

For example a const char * can be given as template argument if static member of a class, can not if defined outside.

I did a small program to test all this, commenting lines that do not compile. I also did a couple of supposition based on compiler output but they might be wrong.

What are the rules of template param values. I saw that the object needed external linkage but a bool is authorized although it obviously doesn't have any kind of linkage.

#include <iostream>
using namespace std;

struct tag {
    static char array[];
    static const char carray[];
    static char *ptr;
    static const char *cptr;
    static const char *const cptrc;
    static string str;
    static const string cstr;
};
char tag::array[] = "array";
const char tag::carray[] = "carray";
char *tag::ptr = (char*)"ptr"; // cast because deprecated conversion
const char *tag::cptr = "cptr";
const char *const tag::cptrc = "cptrc";
string tag::str = "str";
const string tag::cstr = "cstr";


namespace ntag {
    char array[] = "array";
    const char carray[] = "carray";
    char *ptr = (char *)"ptr"; // cast because deprecated conversion
    const char *cptr = "cptr";
    const char *const cptrc = "cptrc";
    string str = "str";
    const string cstr = "cstr";
};

template <class T, T t>
void print() { cout << t << endl; };

int main()
{
    cout << "-- class --" << endl;
    // Works
    print<char *, tag::array>();
    print<const char *, tag::carray>();

    // Does not work because it is a lvalue ?
    // print<char *, tag::ptr>();
    // print<const char *, tag::cptr>();
    // print<const char *const, tag::cptrc>();

    // Template type param must be a basic type ?
    // print<string, tag::str>();
    // print<const string*, tag::cstr>();

    cout << "-- namespace --" << endl;
    // Works
    print<char *, ntag::array>();

    // No external linkage ?
    // print<const char *, ntag::carray>();

    // Does not work because it is an lvalue ?
    // print<char *, ntag::ptr>();
    // print<const char *, ntag::cptr>();
    // print<const char *const, ntag::cptrc>();

    // The type of a template value param must a basic type
    // print<string, ntag::str>();
    // print<const string*, ntag::cstr>();
}
user3127491
  • 153
  • 1
  • 1
  • 6

1 Answers1

8

When using non-type template parameters you need to specify a constant. When the non-type template parameter is a pointer or a reference it is sufficient to specify a constant which can be determined at link-time. In any case, the compiler won't accept anything which can be possibly mutated after link-time. Even variable initialized during link-time are initialized too late:

print<char *, tag::array>();               // OK: the address of the array won't change
print<const char *, tag::carray>();        // OK: the address of the array won't change
print<char *, tag::ptr>();                 // not OK: tag::ptr can change
print<const char *, tag::cptr>();          // not OK: tag::ptr can change
print<const char *const, tag::cptrc>();    // not OK: a [run-time initialized] variable
print<string, tag::str>();                 // not OK: few types are supported (*)
print<const string*, tag::cstr>();         // not OK: tag::cstr has a different type
print<const string*, &tag::cstr>();        // (added) OK: address won't change

print<char *, ntag::array>();              // OK: address of array won't change
print<const char *, ntag::carray>();       // OK: address of array won't change (**)
print<char *, ntag::ptr>();                // not OK: ntag::ptr can change
print<const char *, ntag::cptr>();         // not OK: ntag::cptr can change
print<const char *const, ntag::cptrc>();   // not OK: a [run-time initialized] variable

print<string, ntag::str>();                // not OK: few types are supported (*)
print<const string*, ntag::cstr>();        // not OK: ntag::cstr has a different type
print<const string*, &ntag::cstr>();       // (added) OK: address won't change

Notes:

  • (*) Only integral types, pointers, and references can be used a non-type template parameters. There is no concept of user-define constants which can be used as template parameters.
  • (**) gcc doesn't like this use while clang likes it. gcc not accepting this code seems to be an error! I can't see any restriction which would prohibit the use a const char[] as a template argument. Instead, there is an example in 14.3.2 [temp.arg.nontype] paragraph 2 which is exactly equivalent:

    template<class T, const char* p> class X {
        / ... /
    };
    X<int, "Studebaker"> x1; // error: string literal as template-argument
    const char p[] = "Vivisectionist";
    X<int,p> x2; // OK
    
  • Casting string literals to non-const pointer to char is OK, however, trying to change one of these values is undefined behavior. I strongly recommend not to use this cast!
  • Don't overuse std::endl: in your code is no use for std::endl at all.
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • `not OK: a [run-time initialized] variable` I don't quite understand why a string literal shouldn't count as a constant expression (object of literal type initialized with a constant expression). But isn't it the same problem described [here](http://stackoverflow.com/q/15885399/420683) (address constant expression restrictions)? – dyp Dec 22 '13 at 18:23
  • 2
    DyP: identical string literals may get different addresses even within one translation unit and across translation units merging them may nontrivial depending on the linker which has to be used on the platform. – Dietmar Kühl Dec 22 '13 at 18:32
  • That makes sense, however I think `constexpr char const* cptrc = "hello";` is allowed. This implies that a string literal is a constant expression (an address constant expression: the address of an object with static storage duration), but it's not allowed directly as a non-type argument because of linkage requirements, and it's not allowed indirectly because non-type arguments of pointer type must be of a form `&` *id-expression* or *id-expression* for arrays/functions. – dyp Dec 22 '13 at 18:43
  • (Similarly, `extern int const i = 42; constexpr int const* p = &i; print();` is forbidden via the same restrictions, even though the string-literal rationale doesn't apply.) – dyp Dec 22 '13 at 18:46
  • Thanks for your answer it is very clear. `const string*, tag::cstr` was a copy paste mistake (extra `*`) and const cast for test only. – user3127491 Dec 22 '13 at 18:49
  • Your advise about endl is interesting, it's a pity that no std::nl exist in the stl. – user3127491 Dec 22 '13 at 18:58
  • 1
    I wonder if the pointer to const array is not accepted because it might be optimised at link time to share storage with other identical arrays, making it impossible to distinguish the types in RTTI. – codeshot Sep 06 '15 at 01:42