Passing a String Literal as a Type Argument to a Class Template

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" );

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;
}

C-Style Strings as template arguments?

A string literal cannot be used as a template argument.

Update: Nowadays, a few years after this question was asked and answered, it is possible to use string literals as template arguments. With C++11, we can use characters packs as template arguments (template<char ...c>) and it is possible to pass a literal string to such a template.

This would work, however:

template <char const *str>
struct X
{
const char *GetString() const
{
return str;
}
};

char global_string[] = "String";

int main()
{
X<global_string> x;
cout<<x.GetString();
}

Why passing a string literal to a template calling std::format fails to compile?

After P2216, std::format requires that the format string must be a core constant expression. In your case, the compilation fails because the function argument First is not a constant expression.

The workaround is to use std::vformat, which works for runtime format strings

template<typename First, typename... Args>
auto format1(First&& first, Args&&... args) {
return std::vformat(
std::forward<First>(first),
std::make_format_args(std::forward<Args>(args)...));
}

Demo

If you really want to use std::format, you can pass in a lambda that returns a string literal

template<typename First, typename... Args>
auto format1(First first, Args&&... args) {
return std::format(first(), std::forward<Args>(args)...);
}

int main() {
std::cout << format1([]{ return "hi {} {}"; }, 123, 456);
}

Demo

String literal that depends on the template type parameter?

My first attempt to shoehorn if constexpr in there didn't go well, but a variable template looks fine:

template <typename CharT>
constexpr CharT const *timeFmt;

template <>
constexpr auto timeFmt<char> = "%Y-%m-%d";

template <>
constexpr auto timeFmt<wchar_t> = L"%Y-%m-%d";

template <typename CharT>
void parse(const CharT *str)
{
std::tm date;
std::basic_istringstream<CharT> date_stream{str};
date_stream >> std::get_time(&date, timeFmt<CharT>);
// ...
}

For the record, here is the ugly thing that came out of my first try:

template<typename CharT>
void parse(const CharT *str)
{
std::tm date;
std::basic_istringstream<CharT> date_stream{str};
date_stream >> std::get_time(&date, []{
if constexpr (std::is_same_v<CharT, wchar_t>)
return L"%Y-%m-%d";
else
return "%Y-%m-%d";
}());
// ...
}

Passing a string literal to a template char array parameter

Being able to do this hinges on a little-known feature of C++20: a non-type template parameter can have a class template type, without template arguments specified. CTAD will determine those arguments.

So you create a class templated by size_t N, that has char[N] as a member, is constructible from one, and N is deducible by CTAD.

Example:

// This does nothing, but causes an error when called from a `consteval` function.
inline void expectedNullTerminatedArray() {}

template <std::size_t N>
struct ConstString
{
char str[N]{};

static constexpr std::size_t size = N - 1;

[[nodiscard]] std::string_view view() const
{
return {str, str + size};
}

consteval ConstString() {}
consteval ConstString(const char (&new_str)[N])
{
if (new_str[N-1] != '\0')
expectedNullTerminatedArray();
std::copy_n(new_str, size, str);
}
};

Then you do template <ConstString S> struct A {...};, and use either S.str or S.view() to examine the string.

And here are some extra convenience operators for this class:

template <std::size_t A, std::size_t B>
[[nodiscard]] constexpr ConstString<A + B - 1> operator+(const ConstString<A> &a, const ConstString<B> &b)
{
ConstString<A + B - 1> ret;
std::copy_n(a.str, a.size, ret.str);
std::copy_n(b.str, b.size, ret.str + a.size);
return ret;
}

template <std::size_t A, std::size_t B>
[[nodiscard]] constexpr ConstString<A + B - 1> operator+(const ConstString<A> &a, const char (&b)[B])
{
return a + ConstString<B>(b);
}

template <std::size_t A, std::size_t B>
[[nodiscard]] constexpr ConstString<A + B - 1> operator+(const char (&a)[A], const ConstString<B> &b)
{
return ConstString<A>(a) + b;
}

You can also have template UDLs with this class:

template <ConstString S>
struct ConstStringParam {};

template <ConstString S>
[[nodiscard]] constexpr ConstStringParam<S> operator""_c()
{
return {};
}

// -----

template <ConstString S> void foo(ConstStringParam<S>) {}

foo("Sup!"_c);

Is it possible with C++20 to have a constexpr function return a tuple of types that have static constexpr array's with a value passed though a macro?

It is possible to store a string literal inside of a template argument. I wrote a class that does exactly that a year or so ago. This allows the tuple creation to be constexpr while also offering a quite nice way of passing the strings. This class can also be used to differentiate between two types using a string without modifying the rest of the templated class, which is very useful if you're using typeless handles.

template <std::size_t N>
struct TemplateStringLiteral {
char chars[N];

consteval TemplateStringLiteral(const char (&literal)[N]) {
// Does anyone know of a replacement of std::copy_n? It's from
// <algorithm>.
std::copy_n(literal, N, chars);
}
};

Using this class, it is possible for us to pass the tuple creation this class with a string literal attached to it. This does, sadly, slightly modify your C::x from using the MEMBER define for the function arguments to using the MEMBER define for the template arguments. It is also perfectly possible to just use normal string literals in the template arguments.

struct C
{
// Essentially just f<"abc", "def">();
static constexpr auto x = f<MEMBER(abc), MEMBER(def)>();
};

For the function f we now want to take the parameters as template arguments. So we'll now use the TemplateStringLiteral class to take in the string literals, while also making it a template parameter pack to allow for any amount of parameters.

template <TemplateStringLiteral... literal>
consteval auto f() {
// I'll talk about A in a second. It just holds our string
return std::tuple<A<literal>...> {};
}

Now that we've got a function f that can create a std::tuple from some amount of string literals passed in through template parameters, we'll just need to define the class A that the tuple holds. We can't pass the string literals directly to std::tuple as it does not have something akin to TemplateStringLiteral. Defining class A is very simple, as we only need a str field holding our string literal.

template<TemplateStringLiteral literal>
struct A {
static constexpr auto str = std::to_array(literal.chars);
};

So, using TemplateStringLiteral we've got an implementation that's about 16 lines of C++ and is pretty easily understandable imo.

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.



Related Topics



Leave a reply



Submit