C++11 как реализовать `std::string ToString(std::tuple‹Args› &t)`?

Мне нужна очень удобная функция ToString для многих типов, включая функцию std::tuple. Функция такая:

template <typename T>
inline std::string ToString(const T &t) { 
    std::stringstream ss;
    ss << t;
    return ss.str();
}

template <typename... Args>
inline std::string ToString(const std::tuple<Args...> &t) {
    std::stringstream ss;
    for (int i = 0; i < t.size(); i++) {
        ss << ToString(std::get<i>(t)) << " ";
    }
    return ss.str();
}

Вторая часть неверна в грамматике, как реализовать ее с помощью шаблона С++ 11?

И как реализовать FromString вот так:

template <typename T>
inline T FromString(const std::string &s) {
    std::stringstream ss(s);
    T t;
    ss >> t;
    return t;
}

template <typname... Args>
inline std::tuple<Args...> FromString(const std::string &s) {
    std::tuple<Args...> ret;
    ret.resize(sizeof...Args);
    std::stringstream ss;
    size_t pos;
    for (int i = 0, prev_pos = 0; i < sizeof...Args and prev_pos < s.length(); i++) {
        pos = s.find(" ", prev_pos);
        T t = FromString(s.substr(prev_pos, pos));
        std::get<i>(ret) = t;
        prev_pos = pos
    }
    return ret;
}

Вторая часть также неверна в грамматике С++ 11, как ее реализовать?


person linrongbin    schedule 13.07.2017    source источник
comment
Шаблоны только во время компиляции. Вы не можете использовать переменные времени выполнения в качестве аргументов шаблона. Чтобы решить эту проблему, я рекомендую вам иметь вспомогательную функцию, которая принимает пакет параметров, и вы распаковать кортеж при вызове этой вспомогательной функции.   -  person Some programmer dude    schedule 13.07.2017
comment
В метапрограммировании шаблонов итерация обычно выполняется с помощью рекурсии.   -  person François Andrieux    schedule 13.07.2017
comment
Я плохо разбираюсь в шаблонах С++, поэтому я не уверен, как именно написать код... не могли бы вы показать мне код?   -  person linrongbin    schedule 13.07.2017
comment
Вы можете использовать std::index_sequence   -  person Jarod42    schedule 13.07.2017
comment
Это становится намного проще в C++14 и еще проще в C++17.   -  person Yakk - Adam Nevraumont    schedule 13.07.2017


Ответы (3)


В С++ 17 вы можете сделать:

template <typename ... Ts>
std::string ToString(const Ts& ... ts) { 
    std::stringstream ss;
    const char* sep = "";
    ((static_cast<void>(ss << sep << ts), sep = " "), ...);
    return ss.str();
}

template <typename... Args>
std::string ToString(const std::tuple<Args...> &t) {
    return std::apply([](const auto&... ts) { return ToString(ts...); }, t);
}

Демо

person Jarod42    schedule 13.07.2017
comment
@Holt Пробел в конце - person Passer By; 13.07.2017
comment
@PasserBy Первоначальная версия не удаляла конечные пробелы, хотя обновленная версия удаляет;) - person Holt; 13.07.2017
comment
boost::apply_visitor делает то же самое? это не требует С++ 17 - person linrongbin; 14.07.2017
comment
en.cppreference обеспечивает возможную реализацию. boost::apply_visitor должен посетить variant. - person Jarod42; 14.07.2017
comment
@zhaochenyou Даже если это так, кратные выражения требуют C++17. - person Holt; 14.07.2017
comment
@ Jarod42 Не могли бы вы подробно описать поведение этого конкретного выражения сгиба? Я очень заинтригован. - person Holt; 14.07.2017
comment
Потому что я git, struct evil { friend evil operator<<( std::ostream& os, evil ) { return {}; } template<class X> friend void operator,(evil, X&&){ exit(-1); } } и ToString( evil{} ); всегда выполняйте приведение void перед использованием , в универсальном коде. - person Yakk - Adam Nevraumont; 14.07.2017
comment
@Holt: Сложите выражение с операторной запятой: ss << sep << ts;, за которой следует sep = " " для каждого ts. - person Jarod42; 14.07.2017
comment
Обратите внимание, что в совместимых компиляторах C++ вы можете заменить (static_cast<void>(ss << sep << ts), sep = " ") на [&]{ss << sep << ts); sep = " ";}(), что, на мой взгляд, более понятно; недостатком является то, что многие компиляторы не будут работать, так как они ошибочно не смогут понять, что расширение пакета (из ts...) происходит за пределами лямбды, и выдадут ошибку, говорящую о том, что ts не расширен. - person Yakk - Adam Nevraumont; 14.07.2017

namespace notstd {
  template<std::size_t...Is>
  struct index_sequence {};
  template<std::size_t N, std::size_t...Is>
  struct make_index_sequence:make_index_sequence<N-1,N-1,Is...>{};
  template<std::size_t...Is>
  struct make_index_sequence<0,Is...>:index_sequence<Is...>{};

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

  namespace details {
    template<class F, class Tuple, std::size_t...Is>
    auto apply( F&& f, Tuple&& tuple, index_sequence<Is...> )
    RETURNS( std::forward<F>(f)( std::get<Is>(std::forward<Tuple>(tuple))... ) )
    template<class Tuple>
    using raw_tuple = typename std::remove_cv<typename std::remove_reference<Tuple>::type>::type;
    template<class Tuple>
    using tuple_count = std::tuple_size< raw_tuple<Tuple> >;
  }
  template<class F, class Tuple>
  auto apply( F&& f, Tuple&& tuple )
  RETURNS(
    ::notstd::details::apply(
      std::forward<F>(f),
      std::forward<Tuple>(tuple),
      ::notstd::make_index_sequence<
        ::notstd::details::tuple_count<Tuple>::value
      >{}
    )
  )
}

теперь этот ::notstd::apply ведет себя так же, как std::apply в C++17.

Затем мы приклеиваем это к вашему ToString через ToStream:

struct to_stream_t;

template<class...Args>
void ToStream(std::ostream& os, const std::tuple<Args...>& t) {
  os << '{';
  ::notstd::apply( to_stream_t{os}, t );
  os << '}';
}
inline void ToStream(std::ostream&) {}
template<class T>
void ToStream(std::ostream& os, const T& t) { 
  os << t;
}
template<class T0, class... Ts>
void ToStream(std::ostream& os, const T0& t0, const Ts& ... ts) { 
  ToStream(os, t0);
  using discard=int[];
  (void)discard{0,((
    void(os << ' '), to_stream_t{os}(ts)
  ),0)...};
}
struct to_stream_t {
  std::ostream& os;
  template<class...Args>
  void operator()(Args const&...args) const {
    ToStream(os, args...);
  }
};
template<class...Ts>
std::string ToString( Ts const&... ts ) {
  std::stringstream ss;
  ToStream( ss, ts... );
  return ss.str();
}

это также сглаживает рекурсивные кортежи.

Если вы добавите дополнительные реализации std или базового типа с ручным управлением ToStream, поместите их перед телом to_stream_t, иначе рекурсия не сработает. И рекурсивно через to_stream_t{os}(t) вместо ToStream(os, t) вообще, чтобы найти правильные перегрузки.

Тестовый код:

std::tuple<std::string, std::string, int> t("hello", "world", 42);
std::cout << ToString(t, "-", t);

Живой пример.

Мы можем увеличить векторную поддержку:

template<class T, class A>
void ToStream(std::ostream& os, const std::vector<T, A>& v) {
  os << '[';
  for (auto const& x:v)
  {
    if (std::addressof(x) != v.data())
        os << ',';
    to_stream_t{os}(x);
  }
  os << ']';
}

затем протестируйте все это:

std::tuple<std::string, std::string, int> t("hello", "world", 42);
std::cout << ToString(t, "-", t) << "\n";
std::vector< int > v {1,2,3};
std::cout << ToString(v) << "\n";
std::vector< std::tuple<int, int> > v2 {{1,2},{3,4}};
std::cout << ToString(v2) << "\n";
auto t2 = std::tie( v, v2 );
std::cout << ToString(t2) << "\n";

Живой пример.

Конечный результат:

{hello world 42} - {hello world 42}
[1,2,3]
[{1 2},{3 4}]
{[1,2,3] [{1 2},{3 4}]}

как и ожидалось.

person Yakk - Adam Nevraumont    schedule 13.07.2017
comment
Опечатки @holt исправлены. Я переходил от ToStream к ToStream, чтобы удалить временные std::string, и кое-что пропустил. - person Yakk - Adam Nevraumont; 13.07.2017
comment
@holt ToStream требовалось для поддержки этого, если я хотел поддерживать рекурсивные кортежи. И не поддерживать это отстой! - person Yakk - Adam Nevraumont; 13.07.2017
comment
@holt Может возникнуть проблема с поиском; to_stream_t перемещает точку, в которой мы ищем ToStream внутри to_stream_t, на после объявления каждого ToStream. Если я использую лямбду, мне понадобится ADL, чтобы найти ToStreams, объявленные после лямбды. Я мог бы использовать токен ADL, но это раздражает. to_stream_t кажется проще. - person Yakk - Adam Nevraumont; 13.07.2017
comment
Не знал, что вы писали ранее: P Кстати, ваш примерно в миллиард раз лучше - person Passer By; 13.07.2017
comment
@holt А что вы делаете, когда хотите, чтобы вектор и кортеж взаимно повторяли свое содержимое? Тем более повторяться. Я лучше повторюсь один раз. - person Yakk - Adam Nevraumont; 13.07.2017

В C++11 вы можете захотеть отказаться сделать это

#include<iostream>
#include<tuple>
#include<utility>
#include<sstream>

template<size_t... I>
struct index_sequence {};

template<size_t N, size_t sz, size_t... I>
struct make_index_sequence_
{
    using type = typename make_index_sequence_<N, sz + 1, I..., sz>::type;
};

template<size_t N, size_t... I>
struct make_index_sequence_<N, N, I...>
{
    using type = index_sequence<I...>;  
};

template<size_t N>
using make_index_sequence = typename make_index_sequence_<N, 0>::type;

template<typename Fn, typename Tuple, size_t... I>
auto apply_(Fn&& fn, Tuple&& tup, index_sequence<I...>) -> decltype(fn(std::get<I>(tup)...))
{
    return fn(std::get<I>(tup)...);
}

template<typename Fn, typename Tuple>
auto apply(Fn&& fn, Tuple&& tup) -> decltype(apply_(std::forward<Fn>(fn), std::forward<Tuple>(tup), make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type>::value>{}))
{
    return apply_(std::forward<Fn>(fn), std::forward<Tuple>(tup), make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type>::value>{});
}

Все вышеперечисленное повторно реализует стандартную библиотеку в более новом C++.

template<typename T>
std::string ToString(const T& t)
{ 
    std::stringstream ss;
    ss << t;
    return ss.str();
}

template<typename T, typename... Ts>
std::string ToString(const T& t, const Ts&... ts)
{
    return ToString(t) + ToString(ts...);   
}

template<typename... Ts>
std::string ToString(const std::tuple<Ts...>& tup)
{
    return apply<std::string (*)(const Ts&...)>(ToString, tup);   
}

И это реальная логика.

Текущий

На совершенно новом уровне я понял, насколько хорош синтаксический сахар.

person Passer By    schedule 13.07.2017