作为模板参数的 C 样式字符串?

C 样式字符串可以用作模板参数吗?

我试过:

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


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

虽然我没有收到关于类定义的抱怨,但是实例化产生了 'X' : invalid expression as a template argument for 'str'(VC)。

124434 次浏览

No, you can't work with string literals at compile time. The best you can get are the weird multicharacter literals (e.g. 'abcd') which some compile-time parsers use. They are mentioned in §2.13.2.1:

An ordinary character literal that contains more than one c-char is a multicharacter literal. A multicharac- ter literal has type int and implementation-defined value.

In C++0x there might be ways around this limitations though with the new string literals, Arctic Interactive has an interesting article on that.

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

C++ does not know about strings. It only knowns about "arrays of characters" and there the literal would be the pointer to the array. So if you would use the "value" of your string as template parameter you would actually use the pointer value.

No. According to C++ Standard 14.3.2/1:

A template-argument for a non-type, non-template template-parameter shall be one of:
— an integral constant-expression of integral or enumeration type; or
— the name of a non-type template-parameter; or
— the address of an object or function with external linkage, including function templates and function template-ids but excluding non-static class members, expressed as & id-expression where the & is optional if the name refers to a function or array, or if the corresponding template-parameter is a reference;or
— a pointer to member expressed as described in 5.3.1 .

Strings are not in the list.

You can use address of string with external linkage as a template parameter, e.g.:

template <const char** T> class My {
public:
void do_stuff() {
std::cout << "zzz";
}
};


const char* str;


int main()
{
My<&str> zz;
zz.do_stuff();


printf("Result: %d %d \n",  60 % 10 + 1, (60 % 10 ) + 1 );
}

With C++11 you can fairly sanely represent string literals as variadic template arguments, i.e. a collection of int template parameters. I've put together a proof of concept example that sets up one such template without manually having to write foo<16, 73, 51 ...> for every such string.

Example:

// The template we want to pass a string to
template <int... Args>
struct foo {
// It needs one helper function for decltype magic, this could be avoided though
template <int N>
static foo<N, Args...>  add_one();
};


// This is the string we want to use with foo, simulating foo<"Hello world!" __FILE__>:
constexpr const char *teststr = "Hello world!" __FILE__;


// Get char N of a string literal
constexpr int strchr(const char *str, int N) { return str[N]; }


// recursive helper to build the typedef from teststr
template <int N, int P=0>
struct builder {
typedef typename builder<N, P+1>::type child;
typedef decltype(child::template add_one<strchr(teststr,P)>()) type;
};


template <int N>
struct builder<N,N> {
typedef foo<strchr(teststr, N)> type;
};


// compile time strlen
constexpr int slen(const char *str) {
return *str ? 1 + slen(str+1) : 0;
}


int main() {
builder<slen(teststr)>::type test;
// compile error to force the type to be printed:
int foo = test;
}

You'll need at least gcc 4.6 for constexpr and it could use some polish still but the compiler error I get indicates the type is being built sanely:

error: cannot convert ‘builder<19>::type {aka foo<72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 115, 108, 105, 116, 46, 99, 99, 0>}’ to ‘int’ in initializatio

I known, this topic is a bit old but I put this comment if anyone is interested. I achieved templates with passing literal string as argument with combination of MACROS.

I made a code example,



#include <stdio.h>
#include <iostream>
#include <vector>
#include <memory>
#include <string.h>


using namespace std;


#define MAX_CONST_CHAR 100


#define MIN(a,b) (a)<(b)?(a):(b)


#define _T(s)\
getChr(s,0),\
getChr(s,1),\
getChr(s,2),\
getChr(s,3),\
getChr(s,4),\
getChr(s,5),\
getChr(s,6),\
getChr(s,7),\
getChr(s,8),\
getChr(s,9),\
getChr(s,10),\
getChr(s,11),\
getChr(s,12),\
getChr(s,13),\
getChr(s,14),\
getChr(s,15),\
getChr(s,16),\
getChr(s,17),\
getChr(s,18),\
getChr(s,19),\
getChr(s,20),\
getChr(s,21),\
getChr(s,22),\
getChr(s,23),\
getChr(s,24),\
getChr(s,25),\
getChr(s,26),\
getChr(s,27),\
getChr(s,28),\
getChr(s,29),\
getChr(s,30),\
getChr(s,31),\
getChr(s,32),\
getChr(s,33),\
getChr(s,34),\
getChr(s,35),\
getChr(s,36),\
getChr(s,37),\
getChr(s,38),\
getChr(s,39),\
getChr(s,40),\
getChr(s,41),\
getChr(s,42),\
getChr(s,43),\
getChr(s,44),\
getChr(s,45),\
getChr(s,46),\
getChr(s,47),\
getChr(s,48),\
getChr(s,49),\
getChr(s,50),\
getChr(s,51),\
getChr(s,52),\
getChr(s,53),\
getChr(s,54),\
getChr(s,55),\
getChr(s,56),\
getChr(s,57),\
getChr(s,58),\
getChr(s,59),\
getChr(s,60),\
getChr(s,61),\
getChr(s,62),\
getChr(s,63),\
getChr(s,64),\
getChr(s,65),\
getChr(s,66),\
getChr(s,67),\
getChr(s,68),\
getChr(s,69),\
getChr(s,70),\
getChr(s,71),\
getChr(s,72),\
getChr(s,72),\
getChr(s,72),\
getChr(s,73),\
getChr(s,74),\
getChr(s,75),\
getChr(s,76),\
getChr(s,77),\
getChr(s,78),\
getChr(s,79),\
getChr(s,80),\
getChr(s,81),\
getChr(s,82),\
getChr(s,83),\
getChr(s,84),\
getChr(s,85),\
getChr(s,86),\
getChr(s,87),\
getChr(s,88),\
getChr(s,89),\
getChr(s,90),\
getChr(s,91),\
getChr(s,92),\
getChr(s,93),\
getChr(s,94),\
getChr(s,95),\
getChr(s,96),\
getChr(s,97),\
getChr(s,98),\
getChr(s,99),\
getChr(s,100)


#define getChr(name, ii) ((MIN(ii,MAX_CONST_CHAR))<sizeof(name)/sizeof(*name)?name[ii]:0)


template <char... Chars_>
class E {


public:
string *str;


E(){
std::vector<char> vec = {Chars_...};
str = new string(vec.begin(),vec.end());
}


~E()
{
delete str;
}
};


int main(int argc, char *argv[])
{


E<_T("Any template can pass const strings literals")> e;


printf("%s",e.str->c_str());


}

This works with g++ 4.6 and passing argument -std=c++0x, and have a limit of 100 char but, of course, can be as greater as you want. Maybe this technique is not well optimized, but it will be more productive than declare the needed external variables (I'm sure ;) )

Constraints: The literal string must be one and last argument of the template due the passing of variadics arguments.

EDIT: Thanks to Padek he tested that this piece of code also it works with Visual Studio 2017 but changing strlen by sizeof(name)/sizeof(*name).

Sorry to post on such an old question, but here's what I feel is the cleanest approach to actually pass a literal as the argument without using storage.

Encode the string as a type:

template <char... chars>
using tstring = std::integer_sequence<char, chars...>;

Create a user defined literal operator:

template <typename T, T... chars>
constexpr tstring<chars...> operator""_tstr() { return { }; }

And use partial specialization to recover the character data as needed:

template <typename>
struct X;


template <char... elements>
struct X<tstring<elements...>> {
const char* GetString() const
{
static constexpr char str[sizeof...(elements) + 1] = { elements..., '\0' };
return str;
}
};

This allows you to write:

X<decltype("my_string"_tstr)>

The user defined literal uses non-standard (n3599) functionality not in C++14 but that is supported by recent GCC and Clang builds, and hopefully will be reconsidered for C++1z.