C ++ 17 Variadic模板折叠

时间:2017-05-01 13:22:13

标签: c++ variadic-templates fold c++17

我不明白为什么这不起作用。理解模板和可变参数表达式折叠的人能解释发生了什么并给出一个有效的解决方案吗?

#include <iostream>
#include <string>

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";
    (std::cout << ... << sep << args) << end;
}

int main()
{
    print(1, 2, 3);
}

它应该打印出每个args,其间有一个空格,最后一个换行符。如果删除sep <<,它会起作用,但在打印时每个参数之间没有空格。

7 个答案:

答案 0 :(得分:29)

二进制文件fold-expressions的语法必须是以下之一:

(pack op ... op init)
(init op ... op pack)

你所拥有的是(std::cout << ... << sep << args),它不适合任何一种形式。您需要(cout << ... << pack)之类的内容,这就是删除sep的原因。

相反,您可以折叠逗号:

((std::cout << sep << args), ...);

或使用递归:

template <class A, class... Args>
void print(A arg, Args... args) {
    std::cout << arg;
    if constexpr (sizeof...(Args) > 0) {
        std::cout << sep;
        print(args...);
    }
}

答案 1 :(得分:15)

这样可以,但它会打印一个尾随空格:

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";

    ((std::cout << args << sep), ...) << end;
}

live wandbox example

在这种情况下,正在执行逗号运算符的折叠,从而产生如下扩展:

// (pseudocode)
(std::cout << args<0> << sep), 
(std::cout << args<1> << sep),
(std::cout << args<2> << sep), 
...,
(std::cout << args<N> << sep), 

答案 2 :(得分:10)

你真正想做的是:

std::string sep = " ";
std::string end = "\n";
(std::cout << ... << (sep << args)) << end;

因为您希望(sep << args)左侧折叠std::cout。这不起作用,因为sep << args不知道它正在流式传输到std::cout或流式传输;如果左侧是流,则<<仅流式传输。

简而言之,问题是sep << args不理解它是流媒体。

你的另一个问题是lambda不够。

我们可以解决这个问题。

template<class F>
struct ostreamer_t {
    F f;
    friend std::ostream& operator<<(std::ostream& os, ostreamer_t&& self ) {
        self.f(os);
        return os;
    }
    template<class T>
    friend auto operator<<(ostreamer_t self, T&& t) {
        auto f = [g = std::move(self.f), &t](auto&& os)mutable {
            std::move(g)(os);
            os << t;
        };
        return ostreamer_t<decltype(f)>{std::move(f)};
    }
};

struct do_nothing_t {
    template<class...Args>
    void operator()(Args&&...)const {}
};

const ostreamer_t<do_nothing_t> ostreamer{{}};

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";
    (std::cout << ... << (ostreamer << sep << args)) << end;
}

live example。 (我还使用了sep的文字来确保我使用rvalues。)

ostreamer会抓取对<<'内容的引用,然后将它们转发<<ostream

整个过程对编译器应该是透明的,所以一个不错的优化器应该消除所涉及的一切。

答案 3 :(得分:3)

正如其他人所说,您正在尝试使用错误的 fold-expression 格式。 您可以以非常简单的方式使用lambda帮助程序:

template <typename... Args>
void print(Args&&... args)
{
    std::string sep = " ";
    std::string end = "\n";
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
        std::cout << sep;
        return arg;
    };
    (std::cout << ... << streamSep(args)) << end;
}

这将遵循您编写的代码中预期的行为。但是,如果要在第一个参数之前避免 sep ,可以使用以下命令:

template <typename Arg, typename... Args>
void print(Arg&& arg, Args&&... args)
{
    std::string sep = " ";
    std::string end = "\n";
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
        std::cout << sep;
        return arg;
    };
    std::cout << arg;
    (std::cout << ... << streamSep(args)) << end;
}

答案 4 :(得分:1)

您可以尝试这样的事情

template <typename... Args>
void print(Args... args)
{
  bool first = true;
  auto lambda = [&](auto param)
  {
    if( !first) std::cout << ',';
    first= false;
    return param;
  };

  ((std::cout << lambda(args)), ...);
}

lambda确保分隔符仅插入两个项目之间。

另一方面,如果你不想使用lambdas,你可以重载模板:

template<typename T>
void print(T item)
{
  std::cout << item;
}

template<typename T, typename... Args>
void print(T item, Args... args)
{
  print(item);
  std::cout << ',';
  print(args...);
}

答案 5 :(得分:1)

如果您不想要领先/尾随sep

template <typename First, typename... Rest>
void print(First first, Rest... rest)
{
    std::string sep = " ";
    std::string end = "\n";

    std::cout << first;
    ((std::cout << sep << rest), ...);
    std::cout << end;
}

您需要使std::cout << end;单独指令来处理带有一个参数的案例。

答案 6 :(得分:1)

另一种方法是下一个:

#include <iostream>

template<class U, class... T>
    void printSpaced(const U& u, const T&... args)
{   
     using std::cout;
     using std::endl;         

     ((cout << u) << ... << (cout << ' ', args)) << endl;   
}

这样,您将不会获得前导/后退空间
 用法:

printSpaced(1, 2, "Hello", 4.5f); //Output 1 2 Hello 4.5 and no trailing space