39

Why can't you pass literal strings in here? I made it work with a very slight workaround.

template<const char* ptr> struct lols {
    lols() : i(ptr) {}
    std::string i;
};
class file {
public:
    static const char arg[];
};
decltype(file::arg) file::arg = __FILE__;
// Getting the right type declaration for this was irritating, so I C++0xed it.

int main() {
    // lols<__FILE__> hi; 
    // Error: A template argument may not reference a non-external entity
    lols<file::arg> hi; // Perfectly legal
    std::cout << hi.i;
    std::cin.ignore();
    std::cin.get();
}
Puppy
  • 144,682
  • 38
  • 256
  • 465
  • `const char file::arg[] = __FILE__;` This should allow you drop back to C++03 :). – kennytm Sep 22 '10 at 20:40
  • @Kenny: I tried that. Compiler threw error. – Puppy Sep 22 '10 at 20:41
  • @Dead: Odd. g++ compiles fine (with -pedantic). – kennytm Sep 22 '10 at 20:46
  • @Kenny: Irritating, I always thought this was impossible. Now that I have it, I can't seem to make it do anything interesting or useful. – Puppy Sep 22 '10 at 20:52
  • What about trying const char *const ptr for the template argument instead? After all, it is a literal. – Reinderien Sep 22 '10 at 21:34
  • 5
    Note that if at namespace scope you will need `extern const char arg[] = __FILE__;` to give the array external linkage (const objects have internal linkage by default). – Johannes Schaub - litb Sep 23 '10 at 04:51
  • For those interested in this, David wrote how to do it here — http://cpp-next.com/archive/2012/10/using-strings-in-c-template-metaprograms/ –  May 06 '13 at 12:47

5 Answers5

21

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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 9
    Just begs the question of why they don't just give them the same address. – Puppy Oct 05 '10 at 10:34
  • @DeadMG because string literal "A" might be an immediate in some contexts and not have an address at all? – Jaap Versteegh Jan 22 '13 at 20:50
  • @SlashV I know it's been a while, but could you care to expand on that? I'm sort of also interested in the explanation. – Ed Rowlett-Barbu Apr 10 '13 at 11:40
  • 8
    @Zadirion ..and another while. Given that the template argument needs to have its address taken, the compiler would need to provide a unique address for string literal "A". It would be an unnecessary restriction for the compiler to have to do so. The literal "A" may be a constant in memory somewhere (and have an address), but the compiler may have multiple copies so the address would not be unique, or literal "A" might be the 'immediate' operand of a machine code instruction http://bit.ly/17HySsu and therefore not be in data memory anywhere and thus not have an address at all. – Jaap Versteegh May 06 '13 at 12:48
  • 2
    @SlashV: That's quite wrong. The language already mandates that the compiler merge globals in many contexts (for example inline functions). There would be nothing more or less strenuous about forcing them to pool string literals. As for immediates, a string literal must have program-duration storage, so it would be illegal already to store the contents of a string literal in an immediate. – Puppy May 06 '13 at 15:32
  • I fail to see why ``extern`` is required here. If the string literal is defined as ``static const char * foo = "foo";`` and it is only used inside the same module, why not use it as a template argument value, given that also the resulting template instance can only be used inside the module? Why would ``foo``not be a compile-time constant? Makes no sense to me whatsoever. Also the comments on whether this is "useful" fail to convince me. It can be useful in some scenarios. – BitTickler Mar 13 '15 at 09:35
  • @puppy if they are going to allow strings as template argument, IMO they should not use the address is unique workaround but suppport it natively right away by passing a string value, perhaps as a small user defined class that contains a char array or as a variadic char template as done by user defined literal operator templates, instead of messing with addresses. – Johannes Schaub - litb Mar 13 '15 at 09:47
  • I found this question when I wanted to generate a custom template exception that takes `__LINE__ `as its template. I don't want it to be copy constructible or anything, I just need a way to add a string to my exceptions without actually constructing a string at each check. – Adam Hunyadi Jun 27 '17 at 07:23
14

It is possible, but the the template argument must have external linkage, which precludes using literal strings and mitigates the utility of doing this.

An example I have is:

template<const char* name, const char* def_value=empty_>
struct env : public std::string
{
    env()
    {
        const char* p = std::getenv(name);
        assign(p ? p : def_value);
    }
};

extern const char empty_[] = "";

std::string test = env<empty_>();
Nathan Ernst
  • 4,540
  • 25
  • 38
9

This is how I do it. Makes a lot more sense to me:

struct MyString { static const std::string val; };
const std::string MyString::val = "this is your string";

template<typename T>
void func()
{
  std::cout << T::val << std::endl;
}

void main()
{
  func<MyString>();
}
Yves
  • 11,597
  • 17
  • 83
  • 180
Michael
  • 1,357
  • 3
  • 15
  • 24
  • 1
    Thanks to this brilliant idea I was able to resolve my non-type template parameter issue when I tried to use user-defined type (non-integral type like size_t, int or enum class) before c++20. Thank you! – Quarra Dec 16 '22 at 10:31
5

Good question, thought I'd throw my hat into the ring... I guess you can pass pointers to static variables as non-type template arguments. From C++20 it looks like it won't be an issue... Until then, here is some cheap macro to make it work.

template <const char *Name, typename T>
struct TaggedValue {
  static constexpr char const *name{Name};
  T value;
  friend ostream &operator<<(ostream &o, const TaggedValue &a) {
    return o << a.name << " = " << a.value;
  }
};

#define ST(name, type)\
  const char ST_name_##name[]{#name};\
  using name = TaggedValue<ST_name_##name,type>;

ST(Foo, int);
ST(Bar, int);
ST(Bax, string);

int main() {
  cout << Foo{3} << endl;
  cout << Bar{5} << endl;
  cout << Bax{"somthing"} << endl;
}

C++20 comment (EDIT)

I've not used c++ much lately, so I'm sorry if this isn't 100% correct. There's a comment about why this wouldn't be an issue in c++20. According to the reference on template_parameters:

A non-type template parameter must have a structural type, which is one of the following types (optionally cv-qualified, the qualifiers are ignored):

...

  • a floating-point type;
  • a literal class type with the following properties:
    • all base classes and non-static data members are public and non-mutable and
    • the types of all base classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.

This had led me to believe that the following code would work:

struct conststr
{
    const char * const p;
    template<std::size_t N>
    constexpr conststr(const char(&a)[N]) : p(a)/*, sz(N - 1) */{}
};

template<conststr s>
struct A{};

int main(int argc, char **argv) {
    A<conststr("foo")> x;
}

(Again, I'm not 100% sure if that's 100% correct). But it doesn't, at least not on my machine with g++ -std=c++2a (g++ --version == g++ (Debian 8.3.0-6) 8.3.0). It doesn't work with double either. This guy gives a much more detailed account of the history here, and there are probably better references, and I could just be completely incorrect.

Nathan Chappell
  • 2,099
  • 18
  • 21
  • 1
    Why won't it be an issue from C++20? – saxbophone Aug 27 '21 at 04:21
  • 1
    I've edited my answer to explain that obscure comment. I hope it is useful... – Nathan Chappell Aug 27 '21 at 06:20
  • 1
    Thanks. I should add that I've since been able to pass strings into template parameters (not string literals as such), and I'm not sure if the code I used relied upon C++20 to make it work. It seems to work on all versions of C++03 through C++20. (code: https://godbolt.org/z/jr5K5nvz8) – saxbophone Aug 28 '21 at 00:43
2

This works for classes and, IMO, is useful. The implementation is quick and dirty but can easily be made cleaner:

#include <stdio.h>
#include <string.h>

struct TextTag { const char *text; };

template <const TextTag &TRUE, const TextTag &FALSE>
struct TextTaggedBool
{
  const char *GetAsText() const { return m_value ? TRUE.text: FALSE.text; }
  void SetByText(const char *s) { m_value = !strcmp(s, TRUE.text); }
  bool m_value;
};

class Foo
{
public:
    void method()
    {
        m_tbool.SetByText("True!");  printf("%s\n", m_tbool.GetAsText());
        m_tbool.SetByText("False!"); printf("%s\n", m_tbool.GetAsText());
        m_tbool.m_value = true;  printf("%s\n", m_tbool.GetAsText());
        m_tbool.m_value = false; printf("%s\n", m_tbool.GetAsText());
    }

private:
    static constexpr TextTag TrueTag = { "True!" };
    static constexpr TextTag FalseTag = { "False!" };
    TextTaggedBool<TrueTag, FalseTag> m_tbool;
};

void main() { Foo().method(); }

Output:

True! False! True! False!