【问题标题】:c++11 how to implement `std::string ToString(std::tuple<Args...> &t)`?c++11如何实现`std::string ToString(std::tuple<Args...> &t)`?
【发布时间】:2017-07-13 13:27:57
【问题描述】:

我想要一个非常友好的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();
}

第二部分语法错误,如何用c++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;
}

第二部分c++11语法也错了,怎么实现?

【问题讨论】:

  • 模板仅在编译时。您不能将运行时变量用作模板参数。为了解决这个问题,我建议你有一个带有参数包的辅助函数,然后你 unpack the tuple 调用该辅助函数。
  • 在模板元编程中,迭代通常使用递归来完成。
  • 我不擅长 c++ 模板,所以我不确定如何准确地编写代码...你能给我看代码吗?
  • 你可以使用std::index_sequence
  • 这在 C++14 中变得更容易,在 C++17 中更容易。

标签: c++ c++11 templates stdtuple


【解决方案1】:

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

Demo

【讨论】:

  • @Holt 尾随空格
  • @PasserBy 原始版本没有删除尾随空格,但更新了一个;)
  • boost::apply_visitor 做同样的事情吗?它不需要 c++17
  • en.cppreference 提供了可能的实现。 boost::apply_visitor 是访问variant
  • @Holt:使用运算符逗号折叠表达式:ss &lt;&lt; sep &lt;&lt; ts; 后跟 sep = " ",每个 ts
【解决方案2】:
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 的行为很像 C++17 的 std::apply

然后我们通过ToStream 将其粘贴到您的ToString

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

Live example.

我们可以增加向量支持:

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

Live example.

最终输出为:

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

正如预期的那样。

【讨论】:

  • @holt 拼写错误已修复。我从 ToStream 转换到 ToStream 以删除临时的 std::strings 并错过了一些东西。
  • @holt 如果我想支持递归元组,ToStream 需要支持它。不支持那很糟糕!
  • @holt 可能存在查找问题; to_stream_t 将我们在 to_stream_t 中查找 ToStream 的点移动到 after 每个 ToStream 被声明。如果我使用 lambda,我需要 ADL 来查找在 lambda 之后 声明的 ToStreams。我可以使用 ADL 令牌,但这很烦人。 to_stream_t 似乎更容易。
  • 没有意识到你之前发布的:P BTW 你的好大约十亿倍
  • @holt 当你希望向量和元组在它们的内容上相互递归时你会怎么做?更多的是重复自己。我宁愿重复一次。
【解决方案3】:

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

这些才是真正的逻辑。

Live

让我在一个全新的水平上欣赏语法糖有多棒。

【讨论】:

    猜你喜欢
    • 2014-07-01
    • 2012-05-23
    • 2016-11-05
    • 2011-05-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-25
    • 2014-04-04
    相关资源
    最近更新 更多