【问题标题】:Passing a string literal as a type argument to a class template将字符串文字作为类型参数传递给类模板
【发布时间】:2011-01-03 05:41:09
【问题描述】:

我想声明一个类模板,其中模板参数之一采用字符串文字,例如my_class<"string">.

谁能给我一些可编译的代码来声明一个简单的类模板?


注意:这个问题的previous wording 对于提问者实际上试图完成的事情相当模棱两可,并且可能由于不够清楚而应该被关闭。然而,从那以后,这个问题多次被称为规范的“字符串文字类型参数”问题。因此,它已被重新措辞以同意该前提。

【问题讨论】:

  • 如果您的意思是模板参数,请参阅下面 Neil 的回答;但是,如果您的意思是 ctor 参数(好像您写的那样?),那么您甚至不需要模板。请澄清。
  • 字符串文字 "my string" 的类型为 const char[10]

标签: c++ templates


【解决方案1】:

可以拥有一个const char* 非类型模板参数,并通过static 链接传递给它一个const char[] 变量,这与直接传递字符串文字相差不远。

#include <iostream>    

template<const char *str> 
struct cts {
    void p() {std::cout << str;}
};

static const char teststr[] = "Hello world!";
int main() {
    cts<teststr> o;
    o.p();
}

http://coliru.stacked-crooked.com/a/64cd254136dd0272

【讨论】:

  • 这应该是固定的答案。我希望在我自己发现之前不要错过这个……比我需要这个晚一个月。
  • 全局变量也有明确的标识;字符串文字,不多("hello""hello" 是同一个对象)?
  • @curiousguy:“有时”,但你是对的,这是一个风险/怪癖
  • 使用 constexpr char teststr[] = "Hello world!"; 看起来也不错
【解决方案2】:

进一步来自 Neil 的回答:根据需要使用带有模板的字符串的一种方法是定义一个特征类并将字符串定义为该类型的特征。

#include <iostream>

template <class T>
struct MyTypeTraits
{
   static const char* name;
};

template <class T>
const char* MyTypeTraits<T>::name = "Hello";

template <>
struct MyTypeTraits<int>
{
   static const char* name;
};

const char* MyTypeTraits<int>::name = "Hello int";

template <class T>
class MyTemplateClass
{
    public:
     void print() {
         std::cout << "My name is: " << MyTypeTraits<T>::name << std::endl;
     }
};

int main()
{
     MyTemplateClass<int>().print();
     MyTemplateClass<char>().print();
}

打印

My name is: Hello int
My name is: Hello

【讨论】:

  • 这看起来很有趣。是否可以将字符串作为参数传递并显示和声明对象的示例?
  • @mawg 根据你在 Niel 的回答下的例子(你真的应该更新你的问题,说“更新:这就是我想要的”),你想区分基于(仅)基于字符串的类模板参数。这是不可能的(见尼尔的回答)。但是,如果您想根据 EventId 区分类,那么您可以按照我的回答将 EventName 作为 trait 类中的一个字段。
  • @mawg 另请参阅我的另一个新添加的答案。
  • 谢谢,这正是我需要的 ;)
  • 这正是我所需要的。我使用一个预处理器宏对其进行了一些调整,该宏接受三个参数:类型名称、字符串文字和类模板参数。然后宏定义模板参数的模板特化,将特征设置为字符串文字,最后使用模板参数作为类型名称对类模板进行类型定义。因此,使用这种机制被简化为一个简单的预处理器宏调用,而不是十几行 C++ 模板代码。
【解决方案3】:

抱歉,C++ 目前不支持使用字符串文字(或真实文字)作为模板参数。

但是重新阅读您的问题,这就是您要问的吗?你不能说:

foo <"bar"> x;

但你可以说

template <typename T>
struct foo {
   foo( T t ) {}
};

foo <const char *> f( "bar" );

【讨论】:

  • C++0x 可变参数模板是否支持整数参数?您可能会做一些讨厌的事情,将字符串表示为字符列表。
  • 也许你可能会使用 4 char long 文字(所有编译器都支持?)和可变参数模板——它会在 SO 上给出一个很好的答案,但它在生产代码中看起来很丑:)跨度>
  • 尼尔,这看起来很不错,但既然我很笨,你能更正这段代码吗?我试图让你的参数接受两个参数,这应该足够简单......
     template class Event { public: Event(E eventId, S eventName); // 构造函数 private: E _eventId; char _eventName[MAX_EVENT_NAME_LENGTH + 1]; }; 
    并尝试用
     enum enum1 {eventA, eventB} 实例化; Event testEventA(eventA, "A"); 
    但我得到编译器错误 - 请参阅下一条评论,空间不足
  • t!!如果 PRE 不起作用,如何格式化 cmets? - 尝试实例化 'template 类 - 初始化表达式列表被视为复合表达式 - 从 'const char' 到 'int' 的无效转换 test_fsm.cpp - '(' 之前的声明类型无效令牌 test_fsm.cpp - 'template class Event' 的模板参数使用本地类型 'testFsmClasses::TesEventConstructor()::enum1'
  • @Mawg 编辑您的原始问题。并且永远不要尝试使用 HTML 来格式化问题或答案。使用编辑器上方的那些小按钮。
【解决方案4】:

C++20 fixed_string + "非类型模板参数中的类类型"

显然,对此的提议首先被接受,但随后被删除:"String literals as non-type template parameters"

删除部分是因为它被认为很容易处理另一个被接受的提案:"Class Types in Non-Type Template Parameters"

接受的提案包含一个具有以下语法的示例:

template <std::basic_fixed_string Str>
struct A {};
using hello_A = A<"hello">;

我会尝试用一个例子来更新它,一旦我看到一个支持它的编译器,它就会告诉我任何事情。

A Redditor 还表明,如果您定义自己的 basic_fixed_string 版本,该版本尚未在标准库中,则以下内容可以在 GCC 主机上编译:https://godbolt.org/z/L0J2K2

template<unsigned N>
struct FixedString {
    char buf[N + 1]{};
    constexpr FixedString(char const* s) {
        for (unsigned i = 0; i != N; ++i) buf[i] = s[i];
    }
    constexpr operator char const*() const { return buf; }
};
template<unsigned N> FixedString(char const (&)[N]) -> FixedString<N - 1>;

template<FixedString T>
class Foo {
    static constexpr char const* Name = T;
public:
    void hello() const;
};

int main() {
    Foo<"Hello!"> foo;
    foo.hello();
}

g++ -std=c++2a 9.2.1 from the Ubuntu PPA 编译失败:

/tmp/ccZPAqRi.o: In function `main':
main.cpp:(.text+0x1f): undefined reference to `_ZNK3FooIXtl11FixedStringILj6EEtlA7_cLc72ELc101ELc108ELc108ELc111ELc33EEEEE5helloEv'
collect2: error: ld returned 1 exit status

参考书目:https://botondballo.wordpress.com/2018/03/28/trip-report-c-standards-meeting-in-jacksonville-march-2018/

最后,EWG 决定撤回之前批准的提案,以允许在非类型模板参数中使用字符串文字,因为允许在非类型模板参数中使用类类型的更通用的工具(刚刚获得批准)已经足够好了替换。 (这与上次会议相比有所不同,当时我们似乎都想要两者。)主要区别在于您现在必须将字符数组包装到一个结构中(想想 fixed_string 或类似的),并将其用作您的模板参数类型。 (P0424的用户自定义文字部分还在继续,对允许的模板参数类型进行了相应的调整。)

使用 C++17 if constexpr: if / else at compile time in C++? 会特别酷

这种特性似乎符合 C++20 中令人敬畏的“constexpr Everything”提议,例如:Is it possible to use std::string in a constexpr?

【讨论】:

  • 截至 21 年 8 月,以下编译器支持:msvc v19.29 clang 12.0 gcc 9.1 icx 2021.2.0
  • 在上面的代码中,这一行定义了什么? template&lt;unsigned N&gt; FixedString(char const (&amp;)[N]) -&gt; FixedString&lt;N - 1&gt;;
  • @hutorny 啊,关于支持的好消息!我必须检查!老实说,我不知道这行是什么意思,我只是复制粘贴,但没有尝试理解疯狂的新语法。让我知道你是否能弄明白。
【解决方案5】:

这是一个使用 MPLLIBS 将字符串作为模板参数 (C++11) 传递的解决方案。

#include <iostream>
#include <mpllibs/metaparse/string.hpp> // https://github.com/sabel83/mpllibs
#include <boost/mpl/string.hpp>

// -std=c++11

template<class a_mpl_string>
struct A
{
  static const char* string;
};

template<class a_mpl_string>
const char* A< a_mpl_string >
::string { boost::mpl::c_str< a_mpl_string >::value };  // boost compatible

typedef A< MPLLIBS_STRING ( "any string as template argument" ) > a_string_type;

int main ( int argc, char **argv )
{
  std::cout << a_string_type{}.string << std::endl;
  return 0;
}

打印:

any string as template argument

github上的库:https://github.com/sabel83/mpllibs

【讨论】:

    【解决方案6】:
    inline const wchar_t *GetTheStringYouWant() { return L"The String You Want"; }
    
    template <const wchar_t *GetLiteralFunc(void)>
    class MyType
    {
         void test()
         {
               std::cout << GetLiteralFunc;
         }    
    }
    
    int main()
    {
         MyType<GetTheStringYouWant>.test();
    }
    

    尝试将函数的地址作为模板参数传递。

    【讨论】:

    【解决方案7】:

    编辑:好的,您的问题的标题似乎具有误导性

    "我想要一个类,它的构造函数中有两个参数。第一个可以是 int、double 或 float,所以,第二个总是字符串字面量“我的字符串”,所以我猜是 const char * const 。”

    看起来你正在努力实现:

    template<typename T>
    class Foo
    {
      public:
      Foo(T t,  const char* s) : first(t), second(s)
      {
        // do something
      }
    
      private:
      T first;
      const char* second;
    
    };
    

    这适用于任何类型,对于第一个参数:intfloatdouble 等等。

    现在如果你真的想限制第一个参数的类型只能是intfloatdouble;你可以想出一些更精细的东西,比如

    template<typename T>
    struct RestrictType;
    
    template<>
    struct RestrictType<int>
    {
      typedef int Type;
    };
    
    template<>
    struct RestrictType<float>
    {
      typedef float Type;
    };
    
    template<>
    struct RestrictType<double>
    {
      typedef double Type;
    };
    
    template<typename T>
    class Foo
    {
      typedef typename RestrictType<T>::Type FirstType;
    
      public:
      Foo(FirstType t,  const char* s) : first(t), second(s)
      {
        // do something
      }
    
      private:
      FirstType first;
      const char* second;
    
    };
    
    int main()
    {
      Foo<int> f1(0, "can");
      Foo<float> f2(1, "i");
      Foo<double> f3(1, "have");
      //Foo<char> f4(0, "a pony?");
    }
    

    如果您删除最后一行的注释,您实际上会得到一个编译器错误。


    C++2003 不允许使用字符串字面量

    ISO/IEC 14882-2003 §14.1:

    14.1 模板参数

    非类型模板参数应具有以下类型之一(可选 cv 限定):

    ——整数或枚举类型,

    ——指向对象的指针或指向函数的指针,

    ——对对象的引用或对函数的引用,

    ——指向成员的指针。

    ISO/IEC 14882-2003 §14.3.2:

    14.3.2 模板非类型参数

    非类型、非模板模板参数的模板参数应为以下之一:

    ——整数或枚举类型的整数常量表达式;或

    ——非类型模板参数的名称;或

    — 具有外部链接的对象或函数的地址,包括函数模板和函数模板 ID,但不包括非静态类成员,表示为 & id 表达式,如果名称引用函数或数组,则 & 是可选的,或者如果相应的模板参数是参考;或

    ——指向成员的指针,如 5.3.1 所述。

    [注意:字符串文字 (2.13.4) 不满足任何这些类别的要求,因此不是可接受的模板参数。

    [示例:

    template<class T, char* p> class X { 
      //... 
      X(); 
      X(const char* q) { /* ... */ } 
    }; 
    
    X<int,"Studebaker"> x1; //error: string literal as template-argument 
    char p[] = "Vivisectionist"; 
    X<int,p> x2; //OK 
    

    ——结束示例]——结束注释]

    而且看起来它在即将到来的 C++0X 中不会改变,see the current draft 14.4.2 Template non-type arguments

    【讨论】:

    • Gregory,我在其他地方读过类似的解释,但不是很清楚。你的代码看起来我可以理解,它应该可以工作 - 但它在 Linux 下使用 G++ 会出错(事实上,我有编译错误,上面有两个好看的建议 - 我真的可以这么愚蠢吗?)
    • 我复制了您的代码,但仍然无法正常工作。用于 ONLINE_EVALUATION_BETA2 的 Comeau C/C++ 4.3.10.1(2008 年 10 月 6 日 11:28:09) 版权所有 1988-2008 Comeau Computing。版权所有。 MODE:strict errors C++ C++0x_extensions "ComeauTest.c", line 14: error: expression must have a constant value X x2; //确定
    【解决方案8】:

    您不能将字符串文字直接作为模板参数传递。

    但你可以接近:

    template<class MyString = typestring_is("Hello!")>
    void MyPrint() {
      puts( MyString::data() );
    }
    
    ...
    // or:
    MyPrint<typestring_is("another text")>();
    ...
    

    您只需要一个来自here 的小头文件。


    替代方案:

    • 定义一个全局char const * 并将其作为指针传递给模板。 (here)

      缺点:需要模板参数列表之外的其他代码。不适合,如果需要指定字符串字面量“inline”。

    • 使用非标准语言扩展。 (here)

      缺点:不保证适用于所有编译器。

    • 使用BOOST_METAPARSE_STRING。 (here)

      缺点:您的代码将依赖于 Boost 库。

    • 使用 char 的可变参数模板参数包,例如str_t&lt;'T','e','s','t'&gt;.

      这就是上述解决方案在幕后为您所做的。

    【讨论】:

    • typestring_is 这里是一个类似于BOOST_METAPARSE_STRING的宏
    【解决方案9】:

    根据 Niel 的回答,根据您的 cmets,另一种可能性如下:

    #include <iostream>
    
    static const char* eventNames[] = { "event_A", "event_B" };
    
    enum EventId {
            event_A = 0,
            event_B
    };
    
    template <int EventId>
    class Event
    {
    public:
       Event() {
         name_ = eventNames[EventId];
       }
       void print() {
            std::cout << name_ << std::endl;
       }
    private:
       const char* name_;
    };
    
    int main()
    {
            Event<event_A>().print();
            Event<event_B>().print();
    }
    

    打印

    event_A
    event_B
    

    【讨论】:

    • 是的,这行得通,值得考虑。不过,让我担心的是,可能会犯错误并使它们不对齐。将枚举值和字符串作为一对传递会“更好”(当然,你仍然可以在那里犯错误)
    • 我会使用枚举值和 # 运算符来生成字符串表示形式。
    • 这是最干净的答案,但如果在头文件中使用它会在链接期间导致问题,即重复定义等。接受的答案使用结构上的模板来帮助链接器将定义结合起来编译时间。
    • 请记住,如果两个枚举条目共享相同的基础值,这将不起作用。它们将在 eventNames[] 查找中重叠。
    【解决方案10】:

    使用代理static constexpr const char type_name_str[] = {"type name"}; 将字符串作为模板参数传递。使用[] 定义字符串很重要。

    #include <iostream>
    
    template<typename T, const char* const t_name>
    struct TypeName
    {
    public:
    
        static constexpr const char* Name()         
        {                                   
            return t_name;
        };                                  
    
    };
    
    static constexpr const char type_name_str[] = {"type name"};
    
    int main() 
    {
        std::cout<<TypeName<float, type_name_str>::Name();
        return 0;
    }
    

    【讨论】:

      【解决方案11】:

      我想要一个在其构造函数中接受两个参数的类。第一个可以是 int、double 或 float,so ,第二个始终是字符串字面量 "my string"

      template<typename T>
      class demo
      {
         T data;
         std::string s;
      
         public:
      
         demo(T d,std::string x="my string"):data(d),s(x) //Your constructor
         {
         }
      };
      

      我不确定,但这是你想要的吗?

      【讨论】:

      • 可能想要公开该类的某些部分,或者只是在示例中使用struct,这大大减少了混乱。 :)
      • 看起来非常接近!但是每次实例化时如何传递不同的“我的字符串”作为构造函数参数?
      • @mawg:你不需要pass“我的字符串”。试试这个: demo d(1); // 第二个参数默认为“我的字符串” demo d1(1.4); // 第二个参数默认也是“我的字符串”
      • @mawg:在此处阅读默认参数构造函数:people.cs.vt.edu/~kafura/cs2704/default.html
      【解决方案12】:

      也许不是 OP 所要求的,但如果您使用 boost,您可以创建一个像这样的宏,例如:

      #define C_STR(str_) boost::mpl::c_str< BOOST_METAPARSE_STRING(str_) >::value
      

      然后按如下方式使用:

      template<const char* str>
      structe testit{
      };
      testit<C_STR("hello")> ti;
      

      【讨论】:

      • 这个 C_STR 将如何完全扩展?能举个例子吗?
      【解决方案13】:
      template <char... elements>
      struct KSym /* : optional_common_base */ {
        // We really only care that we have a unique-type and thus can exploit being a `""_ksym singleton`
        const char z[sizeof...(elements) + 1] = { elements..., '\0' };
        // We can have properties, we don't need anything to be constexpr for Rs
      };
      template <typename T, T... chars>
      auto&& operator""_ksym() { 
        static KSym<chars...> kSym; // Construct the unique singleton (lazily on demand)
        return kSym;
      }
      static auto ksym_example1 = "a unique string symbol1\n"_ksym.z;
      static auto ksym_example2 = "a unique string symbol2\n"_ksym.z;
      auto dont_care = []() {
        ::OutputDebugString(ksym_example1);
        ::OutputDebugString("a unique string symbol2\n"_ksym.z);
        assert("a unique string symbol1\n"_ksym.z == ksym_example1);
        assert("a unique string symbol2\n"_ksym.z == ksym_example2);
        return true; 
      }();
      

      以上内容适用于我在 Windows 上使用 Clang 11 进行生产。

      (已编辑)我现在在 Windows 上的 clang 中使用 exactly this

      // P0424R1: http://www.open-std.org/jtc1/SC22/wg21/docs/papers/2017/p0424r1.pdf
      template <char... chars_ta> struct KSymT;
      template <typename T, T... chars_ta> // std::move(KSymT<chars_ta...>::s);
      auto operator""_ksym()->KSymT<chars_ta...>& { return KSymT<chars_ta...>::s; }
      struct KSym {
        virtual void onRegister() {}
        virtual std::string_view zview_get() = 0;
      };
      
      template <char... chars_ta>
      struct KSymT : KSym {
        inline static KSymT s;
        // We really only care that we have a unique-type and thus can exploit being a `""_ksym singleton`
        inline static constexpr char z[sizeof...(chars_ta) + 1] = { chars_ta..., '\0' };
        inline static constexpr UIntPk n = sizeof...(chars_ta);
        // We can have properties, we don't need anything to be constexpr for Rs
        virtual std::string_view zview_get() { return std::string_view(z); };
        //#KSym-support compare with `Af_CmdArgs`
        inline bool operator==(const Af_CmdArgs& cmd) {
          return (cmd.argl[0] == n && memcmp(cmd.argv[0], z, n) == 0);
        }
      };
      

      【讨论】:

        【解决方案14】:

        我一直在为类似的问题而苦苦挣扎,最后想出了一个简洁的实现,将字符串文字解压缩到 char... 模板参数包中,并且不使用 GNU 文字运算符模板扩展:

        #include <utility>
        
        template <char ...Chars>
        struct type_string_t {
            static constexpr const char data[sizeof...(Chars)] = {Chars...};
        };
        
        template <char s(std::size_t), std::size_t ...I>
        auto type_string_impl(std::index_sequence<I...>) {
            return type_string_t<s(I)...>();
        }
        
        #define type_string(s) \
            decltype (type_string_impl<[] -> constexpr (std::size_t i) {return s[i];}> \
                (std::make_index_sequence<sizeof (s)>()))
        
        static_assert (std::is_same<type_string("String_A"),
                                    type_string("String_A")>::value);
        static_assert (!std::is_same<type_string("String_A"),
                                     type_string("String_B")>::value);
        

        一个主要警告:这取决于 C++20 功能(类值作为非类型模板参数;P0732P1907),(截至 2020 年 12 月)仅(部分)在 GCC 中实现9 及更高版本(预处理器功能测试:(__cpp_nontype_template_args &gt;= 201911L) || (__GNUG__ &gt;= 9))。但是,由于该功能是标准的,其他编译器赶上来只是时间问题。

        【讨论】:

        • 你用的是什么编译器?使用 MSVC c++17 编译失败
        • 同意。你用的是什么编译器。不适用于我见过的任何 c++17 编译器。这是因为您不能在模板参数中声明 lambda。
        • 它是 C++20,目前仅适用于 GCC 9 及更高版本。我知道是因为我最近重新发现了类似的东西。
        • 非常好,但无法在 VS 上编译。为了让它工作,我将宏更改为:#define type_string(s) decltype(type_string_impl&lt;[](size_t i) {return s[i];}&gt; (make_index_sequence&lt;sizeof(s)-1&gt;{}))。请注意删除了 -&gt;constexpr 部分。
        【解决方案15】:

        一个字符串文字“我的字符串”,所以我猜是 const char * const

        实际上,具有 n 个可见字符的字符串文字属于 const char[n+1] 类型。

        #include <iostream>
        #include <typeinfo>
        
        template<class T>
        void test(const T& t)
        {
            std::cout << typeid(t).name() << std::endl;
        }
        
        int main()
        {
            test("hello world"); // prints A12_c on my compiler
        }
        

        【讨论】:

        • 这会将字符串文字的 type 作为模板参数传递,而不是字符串文字本身。
        猜你喜欢
        • 2021-09-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-24
        • 1970-01-01
        • 2021-02-24
        • 2011-11-07
        • 2011-07-29
        相关资源
        最近更新 更多