【问题标题】:Concatenating strings (and numbers) in variadic template function在可变参数模板函数中连接字符串(和数字)
【发布时间】:2014-03-15 10:04:29
【问题描述】:

我正在尝试编写一个接受各种字符串或数字的函数(与 std::to_string 一起使用并将它们连接起来。我已经让它只处理字符串,但我无法根据输入进行专门化字符串或数字。

我的代码是这样调用的:

stringer("hello", "world", 2, 15, 0.2014, "goodbye", "world")

这就是我所拥有的:

inline std::string stringer(const std::string &string)
{
    return string;
}

template <typename T, typename... Args>
inline std::string stringer(const std::string &string, T &&val, Args &&...args)
{  
    return stringer(string+std::to_string(val), std::forward<Args>(args)...);
}

template <typename... Args>
inline std::string stringer(const std::string &string, Args &&...args)
{
    return stringer(string, std::forward<Args>(args)...);
}

目前,除非以下都是数字(由于 to_string),否则添加的字符串不止一个。我如何根据字符串或数字进行专业化以完成上述工作?谢谢。

【问题讨论】:

    标签: c++ c++11 variadic-templates variadic-functions


    【解决方案1】:
    inline std::string const& to_string(std::string const& s) { return s; }
    
    template<typename... Args>
    std::string stringer(Args const&... args)
    {
        std::string result;
        using ::to_string;
        using std::to_string;
        int unpack[]{0, (result += to_string(args), 0)...};
        static_cast<void>(unpack);
        return result;
    }
    

    【讨论】:

    • 谢谢,但我收到错误:在 gcc 上调用重载的“to_string”,在 clang 上也出现错误
    • Works like a charm once you let the custom to_string be visible。我打算发布我的解决方案,这就是您实际提出的问题(专门研究 to_string(x) 是否成功),但这要短得多且优雅。
    • @DanielKO 谢谢,哦。您介意为了完整性发布您的解决方案吗?虽然这回答了我的目标,但我仍然可以从你那里学到一些东西
    • @simple static_cast 在这里做什么?
    • @jett 它避免了“未使用的变量”警告。
    【解决方案2】:

    根据请求,这里有一个(更长的)SFINAE 解决方案:

    namespace detail {
        using std::to_string;
    
        std::string
        concat()
        {
            return "";
        }
    
        template<typename Head, typename... Tail>
        decltype( to_string(std::declval<Head>()) )
        concat(Head&& h, Tail&&... t);
    
        template<typename Head, typename... Tail>
        decltype(std::string() + std::declval<Head>())
        concat(Head&& h, Tail&&... t)
        {
            return std::forward<Head>(h) + concat(std::forward<Tail>(t)...);
        }
    
        template<typename Head, typename... Tail>
        decltype( to_string(std::declval<Head>()) )
        concat(Head&& h, Tail&&... t)
        {
            return to_string(std::forward<Head>(h)) + concat(std::forward<Tail>(t)...);
        }
    }
    
    template<typename... Args>
    std::string concat(Args&&... args)
    {
        return detail::concat(std::forward<Args>(args)...);
    }
    

    可以在这里看到实际操作:http://coliru.stacked-crooked.com/a/77e27eaabc97b86b

    请注意,对于给定的类型,它假定字符串连接(使用+)或to_string() 已定义,但不能同时定义两者。所以std::stringconst char* 和任何与std::string 自然交互的第三方字符串类都应该通过+ 版本。当然,如果 3rd 方字符串类做了一些愚蠢的事情,比如定义连接和to_string(),这将是模棱两可的;您需要定义 has_string_concathas_to_string 类型谓词来控制如何解决歧义。

    我还将所有内容都放在一个命名空间中,以便能够使用依赖于参数的查找来选择正确的to_string 版本;完整的例子展示了一个用户定义的类型,它有自己的to_string()

    【讨论】:

      【解决方案3】:

      为什么不使用简单的 std::stringstream ?

      #include <iostream>
      #include <string>
      #include <sstream>
      
      template< typename ... Args >
      std::string stringer(Args const& ... args )
      {
          std::ostringstream stream;
          using List= int[];
          (void)List{0, ( (void)(stream << args), 0 ) ... };
      
          return stream.str();
      }
      
      int main()
      {
          auto s = stringer("hello", ' ', 23, ' ', 3.14, " Bye! " );
      
          std::cout << s << '\n';
      }
      

      【讨论】:

      • 非常好的解决方案。我最初尝试使用字符串流,但在“就地”操作时遇到了麻烦,这使它更加清晰-谢谢!
      • (/* do stuff with args*/, 0) 语法的目的是什么?由于它是逗号运算符,因此它将为可变参数中的每个项目采用表达式的右侧部分?本质上是创建一个零数组?我理解正确吗?
      • @Khurshid 为什么是{(0, ((stream &lt;&lt; args), 0), 0) ...}?那里会发生什么?
      • @romanp:此调用创建(编译时)递归初始化列表构造以创建未使用的、未命名的虚拟 int 数组,但表达式 ( (void)(stream &lt;&lt; args), 0 ) 具有流式传输的副作用将 args 放入流中(在返回第二个 0 之前)。
      • 这是一个了不起的解决方案,有没有人有资源可以谈论这样的聪明东西?
      【解决方案4】:

      另外三种方法可以做到这一点:

      1. 类似于 Khurshid 的,但没有不必要的整数数组
      2. 类似于 Simple 和 Khurshid,但基于旧编译器构建
      3. 递归方式

      还有代码:

      #include <iostream>
      #include <sstream>
      
      // First version:
      template<typename T>
      std::string toString(T value)
      {
          std::ostringstream oss;
          oss << value;
          return oss.str();
      }
      
      std::string merge(std::initializer_list<std::string> strList)
      {
          std::string ret = "";
          for (std::string s : strList) {
              ret += s;
          }
          return ret;
      }
      
      template< typename ... Args >
      std::string stringer1(const Args& ... args)
      {
          return merge({toString(args)...});
      }
      
      
      // Second version:
      template< typename ... Args >
      std::string stringer2(const Args& ... args)
      {
          std::ostringstream oss;
          int a[] = {0, ((void)(oss << args), 0) ... };
      
          return oss.str();
      }
      
      
      // Third version:
      template<typename T>
      std::string stringer3(const T& value)
      {
          std::ostringstream oss;
          oss << value;
          return oss.str();
      }
      
      template<typename T, typename ... Args >
      std::string stringer3(const T& value, const Args& ... args)
      {
          return stringer3(value) + stringer3(args...);
      }
      
      int main()
      {
          int a, b;
          std::cout << stringer1("a", 1) << std::endl;
          std::cout << stringer2("b", 2) << std::endl;
          std::cout << stringer3("c", 3) << std::endl;
      
      // Output:
      //     a1
      //     b2
      //     c3
      }
      

      【讨论】:

        【解决方案5】:

        在 C++ 11 中,以可变参数模板的方式对我有用。它还强制用户提供至少一个参数。如果您在头文件中定义函数,inline 会很有帮助。

        #include <sstream>
        
        template< typename T >
        inline std::string stringify(const T& t)
        {
            std::stringstream string_stream;
            string_stream << t;
            return string_stream.str();
        }
        
        template< typename T, typename ... Args >
        inline std::string stringify(const T& first, Args ... args)
        {
            return stringify( first ) + stringify( args... );
        }
        

        【讨论】:

        • "inline 如果您在头文件中定义函数,会很有帮助。" 不,它不会增加任何内容,因为无论如何模板函数都是隐式内联的:stackoverflow.com/questions/11527415/…跨度>
        【解决方案6】:

        对于 C++17,我们现在有 fold expressions 来简化这个:

        #include <sstream>
        
        // cat - Concatenate values of arbitrary types into a string.
        template <typename... Ts>
        std::string cat(Ts&&... args) {
          std::ostringstream oss;
          (oss << ... << std::forward<Ts>(args));
          return oss.str();
        }
        

        【讨论】:

          猜你喜欢
          • 2020-01-09
          • 2012-03-07
          • 1970-01-01
          • 2014-12-18
          • 2018-11-18
          • 1970-01-01
          • 1970-01-01
          • 2017-05-15
          • 1970-01-01
          相关资源
          最近更新 更多