【问题标题】:C-Style Strings as template arguments? [duplicate]C 样式字符串作为模板参数? [复制]
【发布时间】:2010-12-22 00:56:14
【问题描述】:

C-Style 字符串可以用作模板参数吗?

我试过了:

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)。

【问题讨论】:

  • 这个所谓的重复问题询问如何制作一个接受“构造函数中的两个参数”的模板类 - 他甚至没有说他希望字符串参数成为模板参数!这个问题要清晰得多,简单得多,提名重开。
  • 关闭时我无法正确回答,但是(正如 Matt Bierner 指出的那样),较新的 GCC/clang 版本支持(奇怪的)非标准扩展,用于将字符串文字模板参数提供给用户-定义的文字(不是普通函数)。然后您可以将其转换为 char 数组,如下例所示:template &lt;typename Tchar, Tchar ...str&gt; constexpr int operator"" _len() { const char str2[] = { str..., '\0' }; return c_strlen(str2); } 其中 c_strlen 是 strlen 的 constexpr 版本:constexpr int c_strlen(const char* str) { return *str ? 1 + c_strlen(str + 1) : 0; }
  • 例如constexpr int four = "four"_len; 是 4。如果您只想对字符串进行编译时计算并返回一个值,这很方便。 AFAICT,您不能以这种方式将字符串文字哄骗到普通的模板参数中。可以编写像"hello!"_literal 这样的UDL,它生成一个保存字符串的静态变量,但是在你从operator"" _literal 返回它之后,编译器不再意识到const char* 指向一个变量,所以它不会让您将其用作模板参数。这就是为什么马特的回答涉及一个时髦的tstring 类型和一个decltype 的技巧。

标签: c++ templates string


【解决方案1】:

A string literal cannot be used as a template argument.

更新:如今,在这个问题被问及回答几年后,可以使用字符串文字作为模板参数。在 C++11 中,我们可以使用字符包作为模板参数 (template&lt;char ...c&gt;),并使用 is possible 将文字字符串传递给这样的模板。

但是,这会起作用:

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

【讨论】:

  • 这似乎不起作用,global_string 不会计算为编译时间常数。
  • @Johannes:是的。已编辑。不做测试编译,教我回答。
  • (2 天前更新到我的评论)我发现,任何字符串文字(包括本地文字)都可以将其应用于 template&lt;char ...c&gt;(即这需要 C ++11)。所以是的,我们可以吃蛋糕。 This answer to another question 非常相关。如果您愿意使用 C++14,那么现在可以稍微简化该技术
  • 什么是 C++ 14 方式?
  • 小心,因为代码不是预期的:如果定义了其他值为“String”的字符串,则!std::is_same&lt; X&lt;global_string&gt;, X&lt;global_string2&gt; &gt;。这可能非常令人困惑。
【解决方案2】:

很抱歉在这么老的问题上发帖,但我觉得这是在不使用存储的情况下实际传递文字作为参数的最干净的方法。

将字符串编码为类型:

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

创建用户定义的文字运算符:

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

并根据需要使用部分特化来恢复字符数据:

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

这允许你写:

X<decltype("my_string"_tstr)>

用户定义的文字使用非标准 (n3599) 功能,C++14 中没有,但最近的 GCC 和 Clang 版本支持,希望 C++1z 重新考虑。

【讨论】:

  • 一个理想的解决方案。在这里张贴stackoverflow.com/questions/15858141/…。它更合适。他们的最后一个答案不允许声明类型(由于 lambdas)
  • 顺便说一句,不可能摆脱 const char str,并在 GetString() 处按需构建,以获得零大小的结构吗? [只是出于好奇]
  • 我查看了您的代码生成的 asm 代码。似乎 const char str 构造会产生大量 mov 的 goo.gl/3CfsLQ 。如果使用修改后的 GetString() 似乎更快更小 (sizeof(T) = 1) goo.gl/QdwkEc
  • 谢谢,str 应该是 static constexpr,因为您确实不需要带有字符串的实例数组。
  • 唉,你不能直接在变量中使用static constexpr。二进制死了 coliru.stacked-crooked.com/a/260885406219254d 。而且我不确定这是 gcc/clang 错误...
【解决方案3】:

我知道,这个话题有点老了,但如果有人感兴趣,我会发表评论。我通过结合 MACROS 将文字字符串作为参数实现了模板。

我做了一个代码示例,

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

这适用于 g++ 4.6 和传递参数 -std=c++0x,并且限制为 100 个字符,但当然,可以任意多。也许这种技术没有得到很好的优化,但它比声明所需的外部变量更有效率(我敢肯定;))

约束:由于传递了可变参数参数,文字字符串必须是模板的一个也是最后一个参数。

编辑:感谢 Padek,他测试了这段代码也适用于 Visual Studio 2017,但将 strlen 更改为 sizeof(name)/sizeof( *名称)

【讨论】:

  • 聪明。丑陋……但聪明。
  • 完美,我用过!但是需要修改一下,因为函数strlen(name)是runtime类型,这段代码不能编译,需要替换成:sizeof(name)/sizeof(*name)。
  • @PavelK。您必须使用 gnu 编译器来编译带有 -std=c++0x 的代码。据我所知,由于这个约束 (stackoverflow.com/questions/20968026/…),它不适用于 Windows 编译器(即 cl.exe)。不知道是不是还是和上一版本的Visual Studio不兼容(Visual Studio 2018 Community)...
  • 我使用的是 VS 2017 (Windows),它对诸如“abcdef”[0] 之类的表达式没有问题。我应该只替换 strlen()。
  • 抱歉,我迟迟没有回复您。有用!!!非常感谢测试它@Padek :)
【解决方案4】:

不,您不能在编译时使用字符串文字。你能得到的最好的就是一些编译时解析器使用的奇怪的多字符文字(例如'abcd')。 §2.13.2.1 中提到了它们:

一个普通的字符文字 包含多个 c-char 是 多字符文字。多字符 ter 文字的类型为 int 和 实现定义的值。

在 C++0x 中,可能有一些方法可以绕过这个限制,尽管 new string literalsArctic Interactive 有一篇有趣的文章。

【讨论】:

  • 你能扩展这些“奇怪的 4-character-literals”吗?
  • 添加到答案中,但没有使用它们的编译时解析器文章。
【解决方案5】:

使用 C++11,您可以相当明智地将字符串文字表示为可变参数模板参数,即 int 模板参数的集合。我已经整理了一个概念证明示例,该示例设置了一个这样的模板,而无需为每个这样的字符串手动编写foo&lt;16, 73, 51 ...&gt;

例子:

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

constexpr 至少需要 gcc 4.6,它仍然可以使用一些润色,但我得到的编译器错误表明该类型正在正常构建:

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

【讨论】:

  • 我相当肯定有更聪明的方法可以做到这一点,例如用户定义的文字,但在我写这个答案时,我没有支持它们的编译器
  • 我试过了。用户定义的字符串文字看起来很臭,至少在我使用的 clang 版本中,我认为在语言标准中也是如此。特别是对于字符串,长度参数不被视为常量,因此您依赖空终止。
  • 而且看起来您上面的示例通过将 teststr 参数复制到整个地方而作弊非常糟糕。尝试使其通用,以便它适用于任何字符串文字... ;-)
【解决方案6】:

没有。根据 C++ 标准 14.3.2/1:

非类型、非模板模板参数的模板参数应为以下之一:
— 整数或枚举类型的整数常量表达式;或
— 非类型模板参数的名称;或
— 具有外部链接的对象或函数的地址,包括函数模板和函数模板 ID,但不包括非静态类成员,表示为 & id 表达式,其中 & 是可选的,如果名称指的是函数或数组,或如果相应的模板参数是参考;或
— 指向成员的指针,如 5.3.1 中所述。

字符串不在列表中。

【讨论】:

  • 但是您可以将字符串文字分配给the address of an object with external linkagestackoverflow.com/a/47661368/845092
  • @MooingDuck 对于分配给不同变量的相同字符串,您将获得几个不同的模板实例化。我想这不是人们想要的。
  • @Krill 什么?只有一个字符串(因为它是外部链接),所以只有一个实例化。哦,你的意思是每个字符串一个实例化?但这正是 OP 所要求的!
【解决方案7】:

您可以使用带有外部链接的字符串的地址作为模板参数,例如:

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

【讨论】:

    【解决方案8】:

    C++ 不知道字符串。它只知道“字符数组”,并且文字将是指向数组的指针。因此,如果您将字符串的“值”用作模板参数,您实际上将使用指针值。

    【讨论】:

    • 这也是错误的。字符串文字是一个常量字符数组,作为模板参数完全有效。但问题是文字有内部联系。
    猜你喜欢
    • 2022-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-26
    • 2014-04-16
    • 2011-01-15
    • 1970-01-01
    相关资源
    最近更新 更多