Объединение строк (и чисел) в вариационной функции шаблона

Я пытаюсь написать функцию, которая принимает множество строк или чисел (которые работают с 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). Как я могу специализироваться на основе строки или числа, чтобы сделать работу выше? Благодарю.

Ответ 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;
}

Ответ 2

Почему бы не использовать простой 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';
}

Ответ 3

Еще три способа сделать это:

  • Подобно Хуршиду, но без лишнего массива ints
  • Подобно Simple и Khurshid, но основывается на старых компиляторах
  • Обратный путь

И код:

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

Ответ 4

В запросе здесь (более длинное) решение с 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::string, const char* и любой сторонний строковый класс, который естественно взаимодействует с std::string, должен проходить через версию +. Конечно, если сторонний класс строк делает что-то глупое как определение как конкатенации, так и to_string(), это будет неоднозначно; вам нужно будет определить предикаты типа has_string_concat и has_to_string, чтобы иметь контроль над тем, как разрешить двусмысленность.

Я также помещаю все в пространство имен, чтобы иметь возможность использовать зависящий от аргументов поиск, чтобы выбрать правильную версию to_string; полный пример показывает пользовательский тип со своим to_string().

Ответ 5

В С++ 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... );
}