Question

I'm trying to find a comfortable way to pass string literals as template arguments. I'm not caring about supporting the widest possible number of compilers, I'm using the latest version of g++ with --std=c++0x.

I've tried a lot of possible solutions but all have disappointed me. I'm sort of giving up, but first I'd like to know why a couple of them failed.

Here they are:

#include <iostream>
#include <string>

using namespace std;

struct String {
    char const *m_sz;

    constexpr String(char const *a_sz)
        :
    m_sz(a_sz) {}

    char const *operator () () const {
        return m_sz;
    }
};

template<class _rstr>
string const Get() {
    return _rstr();
}

int main() {
    cout << Get<String("hello")>() << endl;
    return 0;
}

And:

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

int main() {
    String constexpr str = "hello";
    cout << Get<str>() << endl;
    return 0;
}

The goal was to find a comfortable way to pass a string literal to the useless Get function, which returns its template argument as an std::string object.

EDIT: sorry, maybe my main question isn't clear. My question is: why do those two snippets fail?

Was it helpful?

Solution 4

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

OTHER TIPS

You can't use string literals as a template argument, for the simple reason that it's unspecified whether two instances of a literal with the same text are the same object or not. In other words, given:

template <char const* str>
class TC {};

TC< "xyz" > v1;
TC< "xyz" > v2;

It would be unspecified whether v1 and v2 had the same type or not.

You can use char const[] variables as template arguments, however, since they have a defined address:

template <char const* str>
class TC {};

extern char const xyz[] = "xyz";
TC< xyz > v1;
TC< xyz > v2;

In this case, v1 and v2 are guaranteed to have the same type.

EDIT:

I think C++11 removes the need for the extern on the definition of the string, at least if the string and the instantiation are all in the same translation unit. I'm not sure, however; the one time I did something like this, I didn't have access to C++11.

I know the post is old but I haven't found any solution for this problem here, and maybe someone would be interested in my workaround:

template <int N>
constexpr int string_literal_length(const char (&str)[N]) {
   return N - 1;
}

template <int PassedLength, int CountedLength, char... Characters>
struct string_literal {
   static_assert(PassedLength == CountedLength, "Passed to STRING_LITERAL length does not match the length of string...");
};

#define STRING_LITERAL(N, str) string_literal<N, string_literal_length(str), STRING_LITERAL_##N(str)>

// ... as long as we need it ...
#define STRING_LITERAL_128(str) STRING_LITERAL_127(str), str[127]
#define STRING_LITERAL_127(str) STRING_LITERAL_126(str), str[126]
#define STRING_LITERAL_126(str) STRING_LITERAL_125(str), str[125]
#define STRING_LITERAL_125(str) STRING_LITERAL_124(str), str[124]
// ...
#define STRING_LITERAL_5(str) STRING_LITERAL_4(str), str[4]
#define STRING_LITERAL_4(str) STRING_LITERAL_3(str), str[3]
#define STRING_LITERAL_3(str) STRING_LITERAL_2(str), str[2]
#define STRING_LITERAL_2(str) STRING_LITERAL_1(str), str[1]
#define STRING_LITERAL_1(str) str[0]

Now usage:

template <class SLiteral>
struct usage_of_string_literal {
};

int main() {
   usage_of_string_literal<STRING_LITERAL(12, "123456789012")> uosl;
}

Unfortunately one have to provide the length of the string to get it work but it's still more comfortable solution than plain variadic arg template of chars, and the length is verified by the static_assert so the compiler can help to pick appropriate value...


Edit

One more template magic. This one is making use of short-circuit to get rid of the string size from STRING_LITERAL declaration (c++17):

#include <type_traits>
#include <utility>

#define MAX_STRING_LITERAL_LENGTH 11
#define STRING_LITERAL(str) string_literal<char_pack<STRING_LITERAL_11(str)>>::s

#define STRING_LITERAL_11(str) STRING_LITERAL_10(str), ((TERMINATED_10(str))?(str[10]):('\0'))
#define STRING_LITERAL_10(str) STRING_LITERAL_9(str), ((TERMINATED_9(str))?(str[9]):('\0'))
#define STRING_LITERAL_9(str) STRING_LITERAL_8(str), ((TERMINATED_8(str))?(str[8]):('\0'))
#define STRING_LITERAL_8(str) STRING_LITERAL_7(str), ((TERMINATED_7(str))?(str[7]):('\0'))
#define STRING_LITERAL_7(str) STRING_LITERAL_6(str), ((TERMINATED_6(str))?(str[6]):('\0'))
#define STRING_LITERAL_6(str) STRING_LITERAL_5(str), ((TERMINATED_5(str))?(str[5]):('\0'))
#define STRING_LITERAL_5(str) STRING_LITERAL_4(str), ((TERMINATED_4(str))?(str[4]):('\0'))
#define STRING_LITERAL_4(str) STRING_LITERAL_3(str), ((TERMINATED_3(str))?(str[3]):('\0'))
#define STRING_LITERAL_3(str) STRING_LITERAL_2(str), ((TERMINATED_2(str))?(str[2]):('\0'))
#define STRING_LITERAL_2(str) STRING_LITERAL_1(str), ((TERMINATED_1(str))?(str[1]):('\0'))
#define STRING_LITERAL_1(str) str[0]


#define TERMINATED_10(str) TERMINATED_9(str) && str[9]
#define TERMINATED_9(str) TERMINATED_8(str) && str[8]
#define TERMINATED_8(str) TERMINATED_7(str) && str[7]
#define TERMINATED_7(str) TERMINATED_6(str) && str[6]
#define TERMINATED_6(str) TERMINATED_5(str) && str[5]
#define TERMINATED_5(str) TERMINATED_4(str) && str[4]
#define TERMINATED_4(str) TERMINATED_3(str) && str[3]
#define TERMINATED_3(str) TERMINATED_2(str) && str[2]
#define TERMINATED_2(str) TERMINATED_1(str) && str[1]
#define TERMINATED_1(str) str[0]

template <char... Cs>
struct char_pack {
    static constexpr char const arr[sizeof...(Cs) + 1] = {Cs..., 0};
    static constexpr std::size_t non_zero_count = (((Cs != 0)?1:0) + ...);
    static_assert(non_zero_count < MAX_STRING_LITERAL_LENGTH, "You need to create more macros");
};

template <char... Cs>
constexpr char const char_pack<Cs...>::arr[sizeof...(Cs) + 1];

template <char... Cs>
constexpr std::size_t char_pack<Cs...>::non_zero_count;

template <class CP, class = void, class = std::make_index_sequence<CP::non_zero_count>>
struct string_literal;

template <char... Cs, std::size_t... Is>
struct string_literal<char_pack<Cs...>, std::enable_if_t<(Cs && ...)>, std::index_sequence<Is...>> {
    static constexpr char const s[sizeof...(Cs) + 1] = {Cs..., '\0'};
};

template <char... Cs, std::size_t... Is> 
constexpr char const string_literal<char_pack<Cs...>, std::enable_if_t<(Cs && ...)>, std::index_sequence<Is...>>::s[sizeof...(Cs) + 1];

template <char... Cs, std::size_t... Is>
struct string_literal<char_pack<Cs...>, std::enable_if_t<!(Cs && ...)>, std::index_sequence<Is...>>: string_literal<char_pack<char_pack<Cs...>::arr[Is]...>> { };

template <const char *>
struct foo {};

int main() {
    foo<STRING_LITERAL("abcdefghij")> f;
    static_cast<void>(f);
}

[live demo]

You can "simulate" strings with C++11 variadic templates:

template<char... CHARS>
struct string
{
    operator const std::string&()
    {
        static const std::string str{ { CHARS... } };
        return str;
    }
}

int main()
{
    using my_string = string<'h','e','l','l','o',' ','w','o','r','l','d','!','!','!'>;

    std::cout << my_string() << std::endl;
}

This prints:

hello world!!!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top