将const char*作为模板参数传递

40

为什么不能在这里传递文字字符串?我使用了一种非常微小的解决方法使其工作。

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

const char file::arg[] = __FILE__; 这将允许您回退到 C++03 :)。 - kennytm
@Kenny:我尝试过了,编译器报错了。 - Puppy
尝试使用const char *const ptr作为模板参数怎么样?毕竟它是一个字面量。 - Reinderien
5
请注意,如果在命名空间作用域下,您需要使用extern const char arg[] = __FILE__;来为数组提供外部链接(const对象默认具有内部链接)。 - Johannes Schaub - litb
对于那些感兴趣的人,David在这里写了如何做 - http://cpp-next.com/archive/2012/10/using-strings-in-c-template-metaprograms/ - user405725
显示剩余2条评论
5个回答

21

这不会是一个有用的工具,因为它们不符合模板参数的允许形式,所以目前无法使用。

假设它们可以工作。由于它们不需要为相同值使用相同的地址,即使在您的代码中具有相同的字符串字面值,您也将获得不同的实例化。

lols<"A"> n;

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

你可以为你的文本编辑器编写一个插件,将一个字符串替换为逗号分隔的字符字面量列表,再进行还原。使用可变参模板,你可以以某种方式“解决”这个问题。


9
为什么他们不直接把相同的地址给他们呢?这只是在提出一个问题。 - Puppy
8
@Zadirion ..还有另一个。鉴于模板参数需要取其地址,编译器需要为字符串字面值“A”提供唯一的地址。这将是编译器不必要的限制。字面值“A”可能在内存中某个常量位置上(并具有地址),但编译器可能会有多个副本,因此地址不会是唯一的;或者字面值“A”可能是机器码指令的“立即数”操作数 http://bit.ly/17HySsu,因此根本不在数据存储器中,也没有任何地址。 - Jaap Versteegh
2
@SlashV:这是非常错误的。在许多情况下(例如内联函数),语言已经要求编译器合并全局变量。强制它们汇集字符串字面值并没有更加费力或者轻松。至于立即数,字符串字面值必须具有程序持续时间存储,因此将字符串字面值的内容存储在立即数中已经是非法的了。 - Puppy
我不明白为什么这里需要“extern”。如果字符串字面值被定义为“static const char * foo = "foo";”,并且它只在同一模块内使用,为什么不将其用作模板参数值,考虑到生成的模板实例也只能在模块内使用?为什么“foo”不是编译时常量?对我来说毫无意义。此外,关于这是否“有用”的评论也未能说服我。在某些情况下它可能有用。 - BitTickler
如果他们要允许字符串作为模板参数,我认为他们不应该使用地址唯一的解决方法,而是应该立即支持它,通过传递一个字符串值,可能作为一个包含字符数组的小型用户定义类或者像用户定义字面量操作符模板一样的可变字符模板,而不是搞乱地址。 - Johannes Schaub - litb
显示剩余3条评论

15

这是可能的,但模板参数必须具有外部链接,这排除了使用文字字符串并减少了此操作的效用。

我有一个例子:

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

6
除了在namespace std中的几乎所有东西都不应该继承。 - inetknght

11
这是我的做法。对我来说更有意义:
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>();
}

1
由于这个绝妙的想法,我成功地解决了在C++20之前尝试使用用户自定义类型(非整数类型,如size_t、int或枚举类)时遇到的非类型模板参数问题。谢谢! - Quarra

5

好问题,我想分享我的看法... 我想你可以将静态变量的指针作为非类型模板参数传递。从C++20开始似乎不再是问题... 但在那之前,有一些简单的宏可以解决这个问题。

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注释(编辑)

我最近没有经常使用C ++,所以如果有错误请见谅。在c ++ 20中,有一条关于为什么这不是问题的注释。根据template_parameters上的参考:

非类型模板参数必须具有结构类型,其中一种类型是以下类型之一(可以带CV限定词,忽略限定词):

...

  • 浮点类型;
  • 文字类类型,具有以下属性:
    • 所有基类和非静态数据成员均为公共且不可变的,并且
    • 所有基类和非静态数据成员的类型都是结构类型或其(可能是多维的)数组。

这使我相信以下代码将起作用:

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

(我不确定这是否完全正确)。但在我的机器上,使用g++ -std=c++2ag++ --version == g++ (Debian 8.3.0-6) 8.3.0),它并没有起作用。它也不能适用于double。这位网友提供了更详细的历史记录,可能有更好的参考资料,而且我可能完全不正确。

1
为什么从C++20开始这不会是一个问题呢? - saxbophone
1
我已经编辑了我的答案,以解释那个晦涩的评论。希望它有用... - Nathan Chappell
2
谢谢。我应该补充一下,我已经能够将字符串传递到模板参数中(不是像字符串字面量那样),而且我不确定我使用的代码是否依赖于C++20才能使其工作。它似乎可以在所有版本的C++03到C++20上运行。(代码:https://godbolt.org/z/jr5K5nvz8) - saxbophone

2

这适用于类,并且我认为非常有用。实现很快,但可以轻松地变得更加简洁:

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

输出:

真! 假! 真! 假!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接