【问题标题】:Variadic template function to create string创建字符串的可变参数模板函数
【发布时间】:2020-01-09 20:46:54
【问题描述】:

我是可变参数模板函数的新手。我写了一个简单的类StringStream,它有一个可变参数模板函数,它从可变模板参数(字符串、整数等)创建一个std::string

#include <string>
#include <sstream>

class StringStream
{
public:
    StringStream() = default;
    ~StringStream() = default;

    template<typename T>
    std::string Stringify(const T &value)
    {
        mStream << value;
        return mStream.str();
    }

    template<typename T, typename... Ts>
    std::string Stringify(const T& value, Ts... values)
    {
        mStream << value;
        return Stringify(values...);
    }

private:
    std::stringstream mStream;
};

我现在要做的是在StringStream 中使用std::string 成员而不是std::stringstream,并根据Stringify() 的参数构建字符串。对于不是std::string 的参数,我想用std::to_string() 转换为字符串,否则我只是连接参数。我遇到了编译器错误。这是我修改后的课程:

class StringStream
{
public:
    StringStream() = default;
    ~StringStream() = default;

    template<typename T>
    std::string Stringify(const T &value)
    {
        mString += std::to_string(value);
        return mString;
    }

    template<>
    std::string Stringify<std::string>(const std::string& value)
    {
        mString += value;
    }

    template<typename... Ts>
    std::string Stringify(const std::string& value, Ts... values)
    {
        mString += value;
        return Stringify(values...);
    }

    template<typename T, typename... Ts>
    std::string Stringify(const T& value, Ts... values)
    {
        mString += std::to_string(value);
        return Stringify(values...);
    }

private:
    std::string mString;
};

我的编译器错误提示:

error C2665: 'std::to_string': 9 个重载都不能转换所有参数类型

我这样调用函数:

int main()
{
    int age;
    std::cin >> age;
    StringStream ss;
    std::cout << ss.Stringify("I", " am ", age, " years ", "old") << std::endl;
}

有没有办法解决这个问题?

【问题讨论】:

  • 用双引号写的内容是const char*,而不是std::string。您需要单独处理。

标签: c++ c++11 variadic-templates variadic-functions template-argument-deduction


【解决方案1】:

错误的原因是,字符串文字("I"" am "" years ""old")是常量chars(char const [N],对于某些N)的数组。您可以将它们拦截为char const *,但不能拦截为std::string

我想有点离题,但我给你两个建议:

(1) 将Stringify() 分成两个函数:可变参数函数public 调用private 一个(toStr(),在我的以下示例中)以对单参数进行转换

(2) 避免Stringify() 的可变参数版本的递归,而是简单地使用包扩展。

我的意思是......你可以写Stringify()如下

  template <typename... Ts>
  std::string Stringify (Ts const & ... vals)
   {
     using unused = int[];

     (void)unused { 0, (mString += toStr(vals), 0)... };

     return mString;
   }

或者,如果您可以使用 C++17,则使用模板折叠

  template <typename... Ts>
  std::string Stringify (Ts const & ... vals)
   { return ((mString += toStr(vals)), ...); }

对于toStr(),我建议使用std::to_string()但仅在模板T类型不能转换为std::string时启用的模板版本

  template <typename T>
  typename std::enable_if<
     false == std::is_convertible<T, std::string>::value,
     std::string>::type toStr (T const & val)
   { return std::to_string(val); }

以及接受std::string的非模板版本

  std::string toStr (std::string const & val)
   { return val; }

这样,如果一个参数可以直接转换为std::string(是std::string或其他可用于构造std::string的类型),则调用非模板版本;否则称为模板一。

以下是完整的编译示例

#include <iostream>
#include <type_traits>

class StringStream
 {
   private:
      std::string mString;

      template <typename T>
      typename std::enable_if<
         false == std::is_convertible<T, std::string>::value,
         std::string>::type toStr (T const & val)
       { return std::to_string(val); }

      std::string toStr (std::string const & val)
       { return val; }

   public:
      StringStream() = default;
      ~StringStream() = default;

      template <typename... Ts>
      std::string Stringify (Ts const & ... vals)
       {
         using unused = int[];

         (void)unused { 0, (mString += toStr(vals), 0)... };

         return mString;
       }
 };

int main ()
 {
   int age = 42;
   StringStream ss;
   std::cout << ss.Stringify("I", " am ", age, " years ", "old") << std::endl;
 }

【讨论】:

    【解决方案2】:

    你在里面打电话给to_string

    template<typename T>
    std::string Stringify(const T &value)
    {
        mString += std::to_string(value);
        return mString;
    }
    

    这样您就可以从Stringify(const T&amp; value, Ts... values) 中删除to_string,并将其替换为Stringify

    template<typename T, typename... Ts>
    std::string Stringify(const T& value, Ts... values)
    {
        mString += Stringify(value);
        return Stringify(values...);
    }
    

    还为 const char* 添加特化:

    std::string Stringify(const char* value)
    {
        return mString += value;
    }
    

    Live demo

    现在您的问题是 const char[2] 传递了 to_string,但 to_string 没有接受该输入的重载。通过将to_string 替换为Stringify,可以为参数包中的第一个参数使用适当的重载。

    【讨论】:

    • 我必须将 mString += Stringify(value) 更改为 Stringify(value) 然后我得到了我想要的结果。但是,如果我只是使用 std::string 参数调用 Stringify() ,则会出现编译器错误。
    • @jignatius 您收到错误,因为没有重载Stringify 不接受任何参数。您可以添加std::string Stringify() { return ""; },但我不知道它是否是您想要的,返回空字符串。当包中的所有参数都被解包时,Stringify 被空列表调用。
    • 现在我得到了stackoverflow!
    • @jignatius 检查this code,这是你想要的更简单的版本。
    • 那行得通。谢谢。所以 std::string 不需要模板特化?
    猜你喜欢
    • 2014-03-15
    • 1970-01-01
    • 2014-07-07
    • 2021-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-20
    • 1970-01-01
    相关资源
    最近更新 更多