【问题标题】:How can I print a list of elements separated by commas?如何打印以逗号分隔的元素列表?
【发布时间】:2010-08-16 20:18:03
【问题描述】:

我知道如何在其他语言中做到这一点,但在 C++ 中却不知道,我不得不在这里使用它。

我有一组字符串 (keywords) 作为列表打印到out,字符串之间需要一个逗号,而不是尾随逗号。例如,在 Java 中,我会使用 StringBuilder,并在构建字符串后删除末尾的逗号。如何在 C++ 中做到这一点?

auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{
    out << *iter << ", ";
}
out << endl;

我最初尝试插入以下块来执行此操作(将逗号打印移至此处):

if (iter++ != keywords.end())
    out << ", ";
iter--;

【问题讨论】:

  • 我知道它会使行更短,但您确实应该使用for (auto iter = ...;iter 绑定到循环的范围,除非您以后明确打算使用它。
  • @πάνταῥεῖ 虽然它们是重复的,但为什么不反过来呢?这篇文章当然看起来更适合作为欺骗目标。

标签: c++ pretty-print separator


【解决方案1】:

使用中缀迭代器:

// infix_iterator.h 
// 
// Lifted from Jerry Coffin's 's prefix_ostream_iterator 
#if !defined(INFIX_ITERATOR_H_) 
#define  INFIX_ITERATOR_H_ 
#include <ostream> 
#include <iterator> 
template <class T, 
          class charT=char, 
          class traits=std::char_traits<charT> > 
class infix_ostream_iterator : 
    public std::iterator<std::output_iterator_tag,void,void,void,void> 
{ 
    std::basic_ostream<charT,traits> *os; 
    charT const* delimiter; 
    bool first_elem; 
public: 
    typedef charT char_type; 
    typedef traits traits_type; 
    typedef std::basic_ostream<charT,traits> ostream_type; 
    infix_ostream_iterator(ostream_type& s) 
        : os(&s),delimiter(0), first_elem(true) 
    {} 
    infix_ostream_iterator(ostream_type& s, charT const *d) 
        : os(&s),delimiter(d), first_elem(true) 
    {} 
    infix_ostream_iterator<T,charT,traits>& operator=(T const &item) 
    { 
        // Here's the only real change from ostream_iterator: 
        // Normally, the '*os << item;' would come before the 'if'. 
        if (!first_elem && delimiter != 0) 
            *os << delimiter; 
        *os << item; 
        first_elem = false; 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator*() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++(int) { 
        return *this; 
    } 
};     
#endif 

用法类似于:

#include "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));

【讨论】:

  • 酷。像那样。为什么 boost 中没有这样的东西?
  • @Martin:因为我从来没有费心提交它?我可能应该考虑一下...
  • 请提交。 :) 你应该发布到邮件列表并询问是否有人可能想要任何类似的迭代器。
  • @TED:在使用中,它是您发布的代码长度的四分之一,并且它也更加通用(例如,如果您想要制表符分隔的输出,这正是额外的工作)。简而言之,额外的长度主要是给出如何完成工作的一般概念的代码与合理完成并准备好使用的代码之间的差异。
  • 这种功能将在 C++17 中使用 std::experimental::ostream_joiner 提供,目前适用于 GCC 6.0-SVN 和 Clang 3.9-SVN live on Wandbox。请参阅我的新答案。
【解决方案2】:

在即将推出的实验性 C++17 编译器中,您可以使用std::experimental::ostream_joiner

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>

int main()
{
    int i[] = {1, 2, 3, 4, 5};
    std::copy(std::begin(i),
              std::end(i),
              std::experimental::make_ostream_joiner(std::cout, ", "));
}

使用GCC 6.0 SVNClang 3.9 SVN 的实时示例

【讨论】:

  • 我喜欢这个。这是简洁明了的。有没有办法在不使用 std::transform 作为中间存储创建的情况下应用转换?
  • 这是否通过了实验状态?我在任何更高版本中都找不到std::ostream_joiner
【解决方案3】:

因为每个人都决定用 while 循环来做这件事,所以我将举一个 for 循环的例子。

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != keywords.begin()) cout << ", ";
  cout << *iter;
}

【讨论】:

  • 这是规范的容器打印机循环。如果您厌倦了每次都编写它,我们制作了一个 magic helper header,它对所有容器都执行此操作。唯一的评论是,如果一切都足够稳定并且循环后不需要迭代器,请将循环头更改为for (auto iter = keywords.begin(), end = keywords.end(); iter != end; ++iter)
  • 或者,如果您真的想避免每次都进行比较,则可以执行auto it = keywords.begin(); if (it != keywords.end()) cout &lt;&lt; it++;然后使用body @ 运行循环987654325@。不过,就我个人而言,我更喜欢把所有东西都放在一个地方。
  • @Kerrek SB - ...或者您可以只使用中间测试循环。每当您发现自己使用“在第一次(或最后一次)迭代时也这样做”逻辑编写循环时,很有可能您手头上有一个自然的中间测试循环。
  • @TED:什么是“中间测试循环”?
【解决方案4】:

假设一个模糊的正常输出流,因此向它写入一个空字符串确实什么都不做:

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}

【讨论】:

  • 两年后有机会再次查看这个问题,我更喜欢这种方法。聪明。
【解决方案5】:

一种常见的方法是在循环之前打印第一个项目,然后只循环剩余的项目,在每个剩余项目之前预先打印一个逗号。

或者,您应该能够创建自己的流来维护行的当前状态(在 endl 之前)并将逗号放在适当的位置。

编辑: 您还可以按照 T.E.D. 的建议使用中间测试循环。它会是这样的:

if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out << ", ";
        }
    }
}

我首先提到了“在循环之前打印第一项”方法,因为它使循环体非常简单,但是任何方法都可以正常工作。

【讨论】:

  • 你真的应该提到使用中间测试循环的(恕我直言更好)选项。
  • 你不需要if 检查中的else 子句,因为true 分支无论如何都会将你的控制逻辑打破循环。我会写得更像if (iter == keywords.end()) break;。还。如果你要在每次循环迭代中增加一些东西,我发现如果你继续使用 for 循环并将迭代放在人们习惯于看到它并确切知道你是什么的迭代槽中,我会发现它更容易阅读做。
  • ...当然,完成所有这些清理后,您基本上会在下面得到我的答案。它使用五行文本(如果我正确添加了 empty() 检查则为六行),这占用了 17 行。我认为它也更容易理解。它将迭代放在一个标准位置,并摆脱了围绕产生逗号的代码的两个完整级别的嵌套。
  • @T.E.D.我没有使用 for 循环并将增量放在第三条语句中的原因是因为那不起作用:我需要打印项目然后在进行 end 测试之前递增。
  • 这就是为什么你把结束测试放在循环的中间。请参阅下面的答案。
【解决方案6】:

有很多聪明的解决方案,还有太多会在不让编译器完成工作的情况下破坏代码而无法挽救。

显而易见的解决方案是对第一次迭代进行特殊处理:

bool first = true;
for (auto const& e: sequence) {
   if (first) { first = false; } else { out << ", "; }
   out << e;
}

这是一个非常简单的模式:

  1. 不会破坏循环:一眼就能看出每个元素都将被迭代。
  2. 不仅可以放置分隔符,也可以实际打印列表,因为 else 块和循环体可以包含任意语句。

它可能不是绝对最高效的代码,但单个预测良好的分支的潜在性能损失很可能被 std::ostream::operator&lt;&lt; 的庞大庞然大物所掩盖。

【讨论】:

    【解决方案7】:

    这样的?

    while (iter != keywords.end())
    {
     out << *iter;
     iter++;
     if (iter != keywords.end()) cout << ", ";
    }
    

    【讨论】:

    • 没有贬低,但我对这个解决方案的问题是它每次迭代都会对完全相同的条件执行一次检查两次
    • 对同一事物进行两次测试要比在每次迭代中同时针对beginend 进行测试要好。如果编译器可以确定cout &lt;&lt; ", " 没有改变keywordsiter,它可以消除第二个测试。如果您真的想要 DRY,请使用 if ( test ) break;do {} while ( test &amp;&amp; cout &lt;&lt; ", " );,但这些通常被认为是糟糕的风格。
    【解决方案8】:

    我做分隔符的典型方法(任何语言)是使用中间测试循环。 C++ 代码将是:

    for (;;) {
       std::cout << *iter;
       if (++iter == keywords.end()) break;
       std::cout << ",";
    }
    

    (注意:如果关键字可能为空,则需要在循环之前额外检查if

    显示的大多数其他解决方案最终都会在每次循环迭代时进行完整的额外测试。你在做 I/O,所以花的时间不是什么大问题,但它冒犯了我的感受。

    【讨论】:

    • 第一次没有测试条件。这真的是一个do 循环。
    • @Potatoswatter - 我想这取决于您选择如何定义您的条款。对我来说,循环要么经过顶部测试,要么经过底部测试,要么经过中间测试。此循环经过中间测试。至于实现它,在 C 语法语言中,我通常更喜欢使用 for() 循环,除非它恰好是其他形式之一(whiledo)完全匹配的特殊情况。不过,这只是口味问题。
    • 说到被冒犯的感受,您已经省略了一开始所需的一次性测试,以确保keywords.size() &gt; 0 或等效项。这使您的代码看起来比实际上更简单。偷偷摸摸的;-)
    • 顺便说一句:要了解对 do 的需求是多么罕见,请参阅封闭式问题 stackoverflow.com/questions/3347001/do-while-vs-while/…
    • @Mark B - 嗯...我明白了。固定按照您的顺序进行。我从您的代码中获取它,您更喜欢在自己的行上看到iter++,所以请假装我这样做了。 :-) 其他排列也是可能的。
    【解决方案9】:

    在python中我们只写:

    print ", ".join(keywords)
    

    为什么不呢:

    template<class S, class V>
    std::string
    join(const S& sep, const V& v)
    {
      std::ostringstream oss;
      if (!v.empty()) {
        typename V::const_iterator it = v.begin();
        oss << *it++;
        for (typename V::const_iterator e = v.end(); it != e; ++it)
          oss << sep << *it;
      }
      return oss.str();
    }
    

    然后像这样使用它:

    cout << join(", ", keywords) << endl;
    

    与上面的 python 示例不同," " 是一个字符串,keywords 必须是一个可迭代的字符串,在这个 C++ 示例中,分隔符和keywords 可以是任何可流式传输的,例如

    cout << join('\n', keywords) << endl;
    

    【讨论】:

    • typename V::const_iterator 从 C++11 开始甚至可以替换为 auto
    • 我同意,但我倾向于保持与 C++11 之前的标准兼容,因为我已经被旧的 Linux 安装和旧的编译器咬了太多次......
    • 我认为这个答案比大多数有for(...){ if(...){...}else{...} }的答案要好,至少这个答案摆脱了for循环中的跳转指令,感觉更轻更好。
    【解决方案10】:

    我建议你在 lambda 的帮助下简单地切换第一个字符。

    std::function<std::string()> f = [&]() {f = [](){ return ","; }; return ""; };                  
    
    for (auto &k : keywords)
        std::cout << f() << k;
    

    【讨论】:

    【解决方案11】:

    试试这个:

    typedef  std::vector<std::string>   Container;
    typedef Container::const_iterator   CIter;
    Container   data;
    
    // Now fill the container.
    
    
    // Now print the container.
    // The advantage of this technique is that ther is no extra test during the loop.
    // There is only one additional test !test.empty() done at the beginning.
    if (!data.empty())
    {
        std::cout << data[0];
        for(CIter loop = data.begin() + 1; loop != data.end(); ++loop)
        {
            std::cout << "," << *loop;
        }
    }
    

    【讨论】:

    • const_iterator 是与非 const begin 返回的普通 iterator 不同的不兼容类型。
    • 虽然这将在vector::iterator是一个简单指针的平台上编译,但混乱的破坏可能会导致调试模式或更改编译器。
    • @Potatoswatter:你在说什么。这适用于所有编译器(假设它们是 C++ 编译器)。如果您不修改容器的内容,您应该始终更喜欢使用 const_iterator 而不是迭代器。
    • @Martin:我就是这么想的。然后我想,“在标准中哪里说std::vector&lt;std::string&gt;::iterator 可以转换为std::vector&lt;std::string&gt;::const_iterator?现在我担心你确实必须在调用begin() 和@ 之前将data 转换为const Container 987654331@.
    • @Steve(和 Martin):抱歉,这完全错了。 §23.1 中的表 65 要求 iterator 可转换为 const_iterator。我只是不习惯看到它这样写。 (不过,它与重载无关。const_iterator 只是提供了一个转换构造函数。通常我尝试使用单个模板实现 iteratorconst_iterator,并使用 SFINAE 禁用不需要的构造函数。)跨度>
    【解决方案12】:

    为了避免在循环中放置if,我使用了这个:

    vector<int> keywords = {1, 2, 3, 4, 5};
    
    if (!keywords.empty())
    {
        copy(keywords.begin(), std::prev(keywords.end()), 
             std::ostream_iterator<int> (std::cout,", "));
        std::cout << keywords.back();
    }
    

    这取决于矢量类型,int,但您可以使用一些助手将其删除。

    【讨论】:

      【解决方案13】:

      如果值为std::strings,您可以使用range-v3 以声明式风格很好地编写此代码

      #include <range/v3/all.hpp>
      #include <vector>
      #include <iostream>
      #include <string>
      
      int main()
      {
          using namespace ranges;
          std::vector<std::string> const vv = { "a","b","c" };
      
          auto joined = vv | view::join(',');
      
          std::cout << to_<std::string>(joined) << std::endl;
      }
      

      对于必须转换为字符串的其他类型,您只需添加一个转换调用to_string

      #include <range/v3/all.hpp>
      #include <vector>
      #include <iostream>
      #include <string>
      
      int main()
      {
          using namespace ranges;
          std::vector<int> const vv = { 1,2,3 };
      
          auto joined = vv | view::transform([](int x) {return std::to_string(x);})
                           | view::join(',');
          std::cout << to_<std::string>(joined) << std::endl;
      }
      

      【讨论】:

        【解决方案14】:

        您使用的++ 运算符有一点问题。

        你可以试试:

        if (++iter != keywords.end())
            out << ", ";
        iter--;
        

        这样,++ 将在将迭代器与 keywords.end() 进行比较之前进行评估。

        【讨论】:

          【解决方案15】:

          我为此使用了一个小助手类:

          class text_separator {
          public:
              text_separator(const char* sep) : sep(sep), needsep(false) {}
          
              // returns an empty string the first time it is called
              // returns the provided separator string every other time
              const char* operator()() {
                  if (needsep)
                      return sep;
                  needsep = true;
                  return "";
              }
          
              void reset() { needsep = false; }
          
          private:
              const char* sep;
              bool needsep;
          };
          

          使用它:

          text_separator sep(", ");
          for (int i = 0; i < 10; ++i)
              cout << sep() << i;
          

          【讨论】:

            【解决方案16】:

            另一种可能的解决方案,避免if

            Char comma = '[';
            for (const auto& element : elements) {
                std::cout.put(comma) << element;
                comma = ',';
            }
            std::cout.put(']');
            

            取决于你在循环中做什么。

            【讨论】:

              【解决方案17】:

              以下应该做的:-

               const std::vector<__int64>& a_setRequestId
               std::stringstream strStream;
               std::copy(a_setRequestId.begin(), a_setRequestId.end() -1, std::ostream_iterator<__int64>(strStream, ", "));
               strStream << a_setRequestId.back();
              

              【讨论】:

              • .end() - 1 如果序列为空,则为 UB
              【解决方案18】:

              我认为@MarkB 答案的这种变体在可读性、简单性和简洁性之间达到了最佳平衡:

              auto iter= keywords.begin();
              if (iter!=keywords.end()) {
                  out << *iter;
                  while(++iter != keywords.end())
                      out << "," << *iter;
              }
              out << endl;
              

              【讨论】:

                【解决方案19】:

                解决这个问题很容易(取自我的回答here):

                bool print_delim = false;
                for (auto iter = keywords.begin(); iter != keywords.end( ); iter++ ) {
                    if(print_delim) {
                        out << ", ";
                    }
                    out << *iter;
                    print_delim = true;
                }
                out << endl;
                

                我在许多编程语言和所有类型的任务中都使用这个习语(模式?),您需要从列表(如输入)构造分隔输出。让我用伪代码给出摘要:

                empty output
                firstIteration = true
                foreach item in list
                    if firstIteration
                        add delimiter to output
                    add item to output
                    firstIteration = false
                

                在某些情况下,甚至可以完全省略 firstIteration 指示变量:

                empty output
                foreach item in list
                    if not is_empty(output)
                        add delimiter to output
                    add item to output
                

                【讨论】:

                  【解决方案20】:

                  我认为这应该可行

                  while (iter != keywords.end( ))
                  {
                  
                      out << *iter;
                      iter++ ;
                      if (iter != keywords.end( )) out << ", ";
                  }
                  

                  【讨论】:

                  • 这条线是怎么回事:;iter++?此外,这是错误的 - 你是双重附加逗号。这将产生word1,,word2,,word3,
                  【解决方案21】:

                  使用提升:

                  std::string add_str("");
                  const std::string sep(",");
                  
                  for_each(v.begin(), v.end(), add_str += boost::lambda::ret<std::string>(boost::lambda::_1 + sep));
                  

                  你会得到一个包含向量的字符串,用逗号分隔。

                  编辑: 要删除最后一个逗号,只需发出:

                  add_str = add_str.substr(0, add_str.size()-1);
                  

                  【讨论】:

                    【解决方案22】:

                    可能是这样的..

                    bool bFirst = true;
                    for (auto curr = keywords.begin();  curr != keywords.end(); ++curr) {
                       std::cout << (bFirst ? "" : ", ") << *curr;
                       bFirst = false;
                    }
                    

                    【讨论】:

                    • 为什么是有条件的而不是if
                    • 我喜欢简洁。否则我可能会被说服。
                    • 这个每次都修改bFirst
                    【解决方案23】:

                    我认为简单对我来说更好,所以在查看了所有答案后,我准备了我的解决方案(需要 c++14):

                    #include <iostream>
                    #include <vector>
                    #include <utility> // for std::exchange c++14
                    
                    int main()
                    {    
                        std::vector nums{1, 2, 3, 4, 5}; // c++17
                        
                        const char* delim = "";
                        for (const auto value : nums)
                        {
                            std::cout << std::exchange(delim, ", ") << value;
                        }
                    }
                    

                    输出示例:

                    1, 2, 3, 4, 5
                    

                    【讨论】:

                      【解决方案24】:

                      我会选择这样的东西,一个简单的解决方案,应该适用于所有迭代器。

                      int maxele = maxele = v.size() - 1;
                      for ( cur = v.begin() , i = 0; i < maxele ; ++i)
                      {
                          std::cout << *cur++ << " , ";
                      }
                      if ( maxele >= 0 )
                      {
                        std::cout << *cur << std::endl;
                      }
                      

                      【讨论】:

                        【解决方案25】:

                        您可以使用do 循环,重写第一次迭代的循环条件,并使用短路&amp;&amp; 运算符以及有效流是true 这一事实。

                        auto iter = keywords.begin();
                        if ( ! keywords.empty() ) do {
                            out << * iter;
                        } while ( ++ iter != keywords.end() && out << ", " );
                        out << endl;
                        

                        【讨论】:

                        • 好像这样写两个逗号。
                        • @Michael:哎呀,从原始代码中复制粘贴。固定。
                        【解决方案26】:

                        这个重载了流操作符。是的,全局变量是邪恶的。

                        #include <iostream>
                        #include <string>
                        #include <vector>
                        #include <algorithm>
                        #include <iterator>
                        
                        int index = 0;
                        template<typename T, template <typename, typename> class Cont>
                        std::ostream& operator<<(std::ostream& os, const Cont<T, std::allocator<T>>& vec)
                        {
                            if (index < vec.size()) {
                                if (index + 1 < vec.size())
                                    return os << vec[index++] << "-" << vec;
                                else
                                    return os << vec[index++] << vec;
                            } else return os;
                        }
                        
                        int main()
                        {
                            std::vector<int> nums(10);
                            int n{0};
                            std::generate(nums.begin(), nums.end(), [&]{ return n++; });
                            std::cout << nums << std::endl;
                        }
                        

                        【讨论】:

                          【解决方案27】:

                          可以使用函子:

                          #include <functional>
                          
                          string getSeparatedValues(function<bool()> condition, function<string()> output, string separator)
                          {
                              string out;
                              out += output();
                              while (condition())
                                  out += separator + output();
                              return out;
                          }
                          

                          例子:

                          if (!keywords.empty())
                          {
                              auto iter = keywords.begin();
                              cout << getSeparatedValues([&]() { return ++iter != keywords.end(); }, [&]() { return *iter; }, ", ") << endl;
                          }
                          

                          【讨论】:

                            【解决方案28】:

                            c++11 lambda 和宏的组合:

                            #define INFIX_PRINTER(os, sep)([&]()->decltype(os)&{static int f=1;os<<(f?(f=0,""):sep);return os;})()
                            

                            用法:

                            for(const auto& k: keywords)
                                INFIX_PRINTER(out, ", ") << k;
                            

                            【讨论】:

                              【解决方案29】:

                              您可以使用以下两种方法,它们本质上是相同的想法。我喜欢这些方法,因为它们不包含任何不必要的条件检查或赋值操作。我将调用第一个 print first 方法

                              方法一:先打印法

                              if (!keywords.empty()) {
                                  out << *(keywords.begin()); // First element.
                                  for (auto it = ++(keywords.begin()); it != keywords.end(); it++)
                                      out << ", " << *it; // Every subsequent element.
                              }
                              

                              这是我最初使用的方法。它通过自己打印容器中的第一个元素来工作,然后打印每个后续元素,前面有逗号和空格。如果这就是您需要做的所有事情,它很简单,简洁并且效果很好。一旦你想做更多的事情,比如在最后一个元素之前添加一个“and”,这个方法就会失败。您必须检查每个循环迭代是否在最后一个元素上。不过,在列表后添加句点或换行符也不会那么糟糕。您可以在 for 循环之后再添加一行,以将您想要的任何内容附加到列表中。

                              我更喜欢第二种方法。我将把那个称为print last 方法,因为它的作用与第一个方法相同,但顺序相反。

                              方法二:最后打印方法

                              if (!keywords.empty()) {
                                  auto it = keywords.begin(), last = std::prev(keywords.end());
                                  for (; it != last; it++) // Every preceding element.
                                      out << *it << ", ";
                                  out << "and " << *it << ".\n"; // Last element.
                              }
                              

                              这个方法通过使用逗号和空格打印除最后一个元素之外的每个元素,允许您选择在它之前添加一个“and”,在它之后添加一个句点和/或换行符。如您所见,此方法为您提供了更多选择,让您可以在不影响循环性能或添加大量代码的情况下处理最后一个元素。

                              如果你不喜欢将 for 循环的第一部分留空,你可以这样写:

                              if (!keywords.empty()) {
                                  auto it, last;
                                  for (it = keywords.begin(), last = std::prev(keywords.end()); it != last; it++)
                                      out << *it << ", ";
                                  out << "and " << *it << ".\n";
                              }
                              

                              【讨论】:

                                【解决方案30】:

                                我喜欢is_last_elem 测试的基于范围的for。恕我直言,它非常可读:

                                for (auto& e : range)
                                {
                                    if (!is_last_elem(e, range)) [[likely]] 
                                        os << e << ", ";
                                    else
                                        os << e;
                                }
                                os << std::endl;
                                

                                完整代码:

                                C++20:

                                #include <iostream>
                                #include <list>
                                #include <ranges>
                                #include <utility>
                                #include <type_traits>
                                #include <memory>
                                
                                template <std::ranges::bidirectional_range R>
                                bool is_last_elem(const std::ranges::range_value_t<R>& elem, const R& range)
                                {
                                    auto last_it = range.end();
                                    std::advance(last_it, -1);
                                    return std::addressof(elem) == std::addressof(*last_it);
                                }
                                
                                template <std::ranges::bidirectional_range R, class Stream = std::ostream>
                                void print(const R& range, std::ostream& os = std::cout)
                                {
                                    for (auto& e : range)
                                    {
                                        if (!is_last_elem(e, range)) [[likely]] 
                                            os << e << ", ";
                                        else
                                            os << e;
                                    }
                                    os << std::endl;
                                }
                                
                                int main()
                                {
                                    std::list<int> v{1, 2, 3, 4, 5};
                                    print(v);
                                }
                                

                                C++17:

                                #include <iostream>
                                #include <list>
                                #include <utility>
                                #include <type_traits>
                                #include <memory>
                                
                                template <class Range>
                                using value_type_t = std::remove_reference_t<decltype(*std::begin(std::declval<Range>()))>;
                                
                                template <class Range>
                                bool is_last_elem(const value_type_t<Range>& elem, const Range& range)
                                {
                                    auto last_it = range.end();
                                    std::advance(last_it, -1);
                                    return std::addressof(elem) == std::addressof(*last_it);
                                }
                                
                                template <class Range, class Stream = std::ostream>
                                void print(const Range& range, std::ostream& os = std::cout)
                                {
                                    for (auto& e : range)
                                    {
                                        if (!is_last_elem(e, range))
                                            os << e << ", ";
                                        else
                                            os << e;
                                    }
                                    os << std::endl;
                                }
                                
                                int main()
                                {
                                    std::list<int> v{1, 2, 3, 4, 5};
                                    print(v);
                                }
                                

                                【讨论】:

                                  猜你喜欢
                                  • 2011-11-23
                                  • 1970-01-01
                                  • 2021-07-20
                                  • 2021-10-25
                                  • 2021-05-10
                                  • 2019-10-28
                                  • 1970-01-01
                                  • 2018-02-27
                                  相关资源
                                  最近更新 更多