Passing Const Char* as Template Argument

Passing const char* as template argument

Because this would not be a useful utility. Since they are not of the allowed form of a template argument, it currently does not work.

Let's assume they work. Because they are not required to have the same address for the same value used, you will get different instantiations even though you have the same string literal value in your code.

lols<"A"> n;

// might fail because a different object address is passed as argument!
lols<"A"> n1 = n;

You could write a plugin for your text editor that replaces a string by a comma separated list of character literals and back. With variadic templates, you could "solve" that problem this way, in some way.

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.

How to pass const char[] arrays as constexpr template parameters using C++ STL libraries?

I would like to stop writing/hard coding the constexpr array size 7

In C++17, you might use auto as non template parameter and get rid of hard-coded 7:

template <std::size_t PathIndex, const auto& path>
constexpr std::size_t findlastslash()
{
if constexpr (PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\\') {
return PathIndex;
}
else {
return findlastslash<PathIndex - 1, path>();
}
}

template <const auto& path>
constexpr std::size_t startfindlastslash()
{
return findlastslash<std::extent_v<std::remove_reference_t<decltype(path)>> - 1, path >();
}

int main() {
static constexpr const char path[] = "c/test";
static_assert(startfindlastslash<path>() == 1, "Fail!" );
}

Demo

You might "protect" the auto with SFINAE or with concept (C++20):

template <typename T, std::size_t N>
constexpr std::true_type is_c_array_impl(const T(&)[N]) { return {}; }

template <typename T>
constexpr std::false_type is_c_array_impl(const T&) { return {}; }

template <typename T>
constexpr auto is_c_array() -> decltype(is_c_array_impl(std::declval<const T&>())) {return {};}

template <typename T>
concept CArrayRef = (bool) is_c_array<const T&>() && std::is_reference_v<T>;

template <std::size_t PathIndex, const auto& path>
constexpr std::size_t findlastslash() requires (CArrayRef<decltype(path)>)
{
if constexpr (PathIndex < 1 || path[PathIndex] == '/' || path[PathIndex] == '\\') {
return PathIndex;
}
else {
return findlastslash<PathIndex - 1, path>();
}
}

template <const auto& path>
constexpr std::size_t startfindlastslash() requires (CArrayRef<decltype(path)>)
{
return findlastslash<std::extent_v<std::remove_reference_t<decltype(path)>> - 1, path >();
}

Demo

Do I really need to define my own FixedString class? The C++ STL (Standard Template Library) does not have anything which can be used instead for this common/simple task?

It seems there is not currently equivalent to FixedString.

Passing const char* in variadic template argument list results in linker errors

The problem was with defining the template constructor inside a separate .cpp file.
As G.M. linked to this post in the comments, template members should be implemented in the header file or, when defined in the .cpp file, at least be defined explicitly for each instance.

char const *str' as template argument

Because template classes are based on types and not on values.

templates are meant to help you write a more generic code, one that is the same for several types of data so you don't have to write it for each type all over again.

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.

Const char array with template argument size vs. char pointer

The intent of passing any raw pointer to a function is the caller has some idea of:

  • What it points to.
  • How many it points to.

C-style strings as input parameters have the latter inferred, as the assumption is that "how many" is deemed by arrival at the null char terminator.

But what if you're not passing a C-style string? What if it is simply a sequence of zero-or-more char values? Well, if that is the case, then:

void f(const char *s)
{
// how many char does s refer to?
}

The logical deduction would be to do this:

void f(const char *s, std::size_t N)
{
// Now the caller is telling us there are N chars at s
}

and this is not uncommon at all, albeit a potential point of error if the caller passes us the wrong length (never say never).

But what if there were a way to slurp that data from the actual variable type being passed to the function using deduction via non-type template parameter? What if the caller is invoking us with a fixed array?

template<std::size_t N>
void f(const char(&ar)[N])
{
// we know the caller is passing a const-reference to a
// char array declared of size N. The value N can be used
// in this function.
}

Now we know both items in our list: the "what" and the "how many". Furthermore, we can now provide both a template function and an overload and have both worlds available to us:

// length specified implementation
void f(const char *s, std::size_t N)
{
// caller passed N
}

// fixed buffer template wrapper
template<std::size_t N>
void f(const char(&ar)[N])
{
f(ar,N); // invokes length-specified implementation from above.
}

And both of the following will work:

int main()
{
char buff[3];

f(buff,3);
f(buff);

}

So how is this good? Because the following will flag a compiler error, as no matching implementation can be found:

int main()
{
char buff[3];
const char *ptr = buff;
f(ptr); // ERROR: no matching function f(const char *)
}

In summary it is a common technique to assist in providing both items in our bullet list to the callee: the "what" and the "how much", without having to long-hand sizeof(ar)/sizeof(*ar) every time you use a fixed-length native array as your input parameter.

Best of luck.



Related Topics



Leave a reply



Submit