在可变参数模板函数中连接字符串(和数字)

时间:2014-02-16 02:34:47

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

我正在尝试编写一个带有各种字符串或数字的函数(与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)。如何根据字符串或数字进行专门化以完成上述工作?谢谢。

6 个答案:

答案 0 :(得分:15)

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

答案 1 :(得分:12)

为什么不使用简单的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';
}

答案 2 :(得分:6)

另外三种方法:

  1. 与Khurshid相似,但没有不必要的整数
  2. 与Simple和Khurshid类似,但建立在较旧的编译器上
  3. Recurent way
  4. 代码:

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

答案 3 :(得分:3)

根据请求,这是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自然交互的任何第三方字符串类都应通过+版本。当然,如果第三方字符串类做了一些愚蠢的事情,比如定义连接和to_string(),这将是模棱两可的;您需要定义has_string_concathas_to_string类型谓词,以控制如何解决歧义。

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

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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