【问题标题】:Variadic Recursive Template可变参数递归模板
【发布时间】:2018-04-12 00:16:53
【问题描述】:

我有一种特殊的格式,它需要带有最终空终止符的空格分隔标记(空是输出的一部分)。我创建了一个函数来将一系列空格分隔的标记发送到输出流:

// C++ variadic template function to output tokens to a stream delimited by spaces
template <typename T>
void join(std::ostream& os, T const& arg)
{
  // This is the last argument, so insert a null in the stream to delimit
  os << arg << '\000';
}

// Join one, add a space, try again.
template <typename T, typename... Args>
void join(std::ostream& os, T const& arg, Args... args)  // recursive variadic function
{
  os << arg << " ";
  join(os, args...);
}

这适用于join(os, 1, foo, "THING", M_PI); 之类的东西。但是,我也有一些不需要用空格分隔的标记,例如join(os, "KEY=", val);

我尝试使用参数,想也许我可以在参数列表中填充一个nullptr 并使用它来重载方法,跳过空格,但我一生都无法弄清楚如何做到这一点有点过载。

我见过的大多数关于可变参数模板的问题都相当老了,在标准合规性更加普遍之前。如果这已在其他地方得到回答,请指出我。我正在使用 GCC 7.3。

或者,告诉我我把应该很简单的事情过于复杂化了。也许可变参数模板不是这个螺栓的正确锤子?

【问题讨论】:

  • 他们有不同的行为,不要把他们塞进同一个函数里

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


【解决方案1】:

引入一个助手

template <typename A, typename B>
struct pack_t {
  const A& a;
  const B& b;
};

template <typename A, typename B>
std::ostream& operator<<(std::ostream& os, pack_t<A, B> pac) {
  return os << pac.a << pac.b; 
}

template <typename A, typename B>
auto pack(const A& a, const B& b) noexcept {
  return pack_t<A, B>{a, b}; 
}

像这样使用它

join(std::cout, "Hello", "World", pack("pi=", 3.14));

完整代码 (live)

#include <iostream>

template <typename A, typename B>
struct pack_t {
  const A& a;
  const B& b;
};

template <typename A, typename B>
std::ostream& operator<<(std::ostream& os, pack_t<A, B> pac) {
  return os << pac.a << pac.b; 
}

template <typename A, typename B>
auto pack(const A& a, const B& b) noexcept {
  return pack_t<A, B>{a, b}; 
}

template <typename T>
void join(std::ostream& os, const T& arg) {
  os << arg << '\0';
}

template <typename T, typename... Args>
void join(std::ostream& os, const T& arg, const Args&... args) {
  os << arg << ' ';
  join(os, args...);
}

int main() {
  join(std::cout, "Hello", "World", pack("pi=", 3.14));
}

请注意,您可以通过聚合或继承将pack_t 基于std::tuple 来扩展帮助程序以支持两个以上的参数。例如,

namespace impl {

template <typename T, std::size_t... Idx>
struct pack_t {
  T v;
};

template <std::size_t... Idx, typename... Ts>
auto pack(std::index_sequence<Idx...>, Ts&&... vs) noexcept {
  auto v = std::forward_as_tuple(std::forward<Ts>(vs)...);
  return pack_t<decltype(v), Idx...>{std::move(v)};
}

template <typename T, std::size_t... Idx>
std::ostream& operator<<(std::ostream& os, pack_t<T, Idx...> args) {
  return ((os << std::get<Idx>(std::move(args.v))), ...);
}

}

template <typename... Ts>
auto pack(Ts&&... vs) noexcept {
  return impl::pack(
    std::index_sequence_for<Ts...>{}, std::forward<Ts>(vs)...);
}

现在,您可以打包不同数量的参数 (live)。

【讨论】:

  • 我喜欢这个,因为它读起来更自然。我明天上班试试这个。
  • @jwm 更新了pack(),支持不同数量的参数:)
【解决方案2】:

我想你可以选择一个特殊的类型来跳过前面的空格;例如,您可以定义 no_space_tag 类或结构

struct no_space_tag {};

并添加一个join() 版本,该版本在第二个位置(第三个,计数os)拦截no_space_tag 对象,并且不在第一个(第二个)元素之后添加空格;我是说

template <typename T, typename ... Args>
void join (std::ostream & os, T const & arg, no_space_tag const &,
           Args ... args)
 {
   os << arg;
   join(os, args...);
 }

所以你可以通过这种方式添加no_space_tag 对象

join(std::cout, "KEY=", no_space_tag{}, val);

也可以这样

no_space_tag  nst;

join(std::cout, "KEY=", nst, val, ',', "KEY=", nst, val2);

避免添加前面的空格。

以下是一个完整的工作示例

#include <iostream>

struct no_space_tag {};

template <typename T>
void join (std::ostream & os, T const & arg)
 { os << arg << '\000'; }

template <typename T, typename ... Args>
void join (std::ostream & os, T const & arg, Args ... args)
 {
   os << arg << " ";
   join(os, args...);
 }

template <typename T, typename ... Args>
void join (std::ostream & os, T const & arg, no_space_tag const &,
           Args ... args)
 {
   os << arg;
   join(os, args...);
 }

int main()
 {
   char foo {'a'};
   long val { 42L };

   join(std::cout, 1, foo, "THING", "M_PI");
   join(std::cout, "KEY=", no_space_tag{}, val);
 }

【讨论】:

  • 这正是我想要做的。为此,我尝试使用 nullptr (std::nullptr_t),但我在编译时遇到了一些问题。我怀疑模板重载的顺序(或我的模板规范)是问题所在。感谢您证明我最初的想法是可行的。
【解决方案3】:

你可以使用这样的东西。 First 参数要求你至少传递一个参数,但我认为这是有道理的。

#include <iostream>
#include <array>
#include <ostream>
#include <cmath>

template<typename First, typename ... Rest>
void join(std::ostream& _s, bool _sep, First&& _f, Rest&& ... _rest)
{
    _s << _f;
    std::array<int, sizeof...(_rest)> status{(_s << (_sep ? " " : "")  << std::forward<Rest>(_rest), 0) ...};
    _s << '\n';
}

int main()
{
    join(std::cout, true, "These", "are", "some", "args");
    join(std::cout, true, "These", "are", 4, "args");
    join(std::cout, true, "pi", "is", "a", "constant", "=", M_PI);
    join(std::cout, false, "KEY=", "val");

    return 0;
}

打印出来

These are some args
These are 4 args
pi is a constant = 3.14159
KEY=val

【讨论】:

  • @PasserBy 是的,我刚刚意识到第二部分我没有解决,所以我更新了答案
猜你喜欢
  • 2017-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-18
  • 2017-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多