koturnの日記

普通の人です.ブログ上のコードはコピペ自由です.

C++の配列・STLコンテナで区切り文字を入れてostreamに出力する

はじめに

C++において,たまに std:coutstd:ofstreamstd::ostringstream といった出力ストリームに,配列や std::vector の要素を何かの区切り文字を入れて出力したいことがある. この「区切り文字を入れて出力」というのは少々面倒で,末端要素の後に区切り文字を入れないようにしないといけない. 可能な限り,そういった処理を簡潔に記述したいものである.

for

インデックスベースのforを利用するなら以下のように書くだろう.

#include <iostream>
#include <vector>


int
main()
{
  std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  if (!v.empty()) {
    for (decltype(v)::size_type i = 0; i < v.size() - 1; i++) {
      std::cout << v[i] << ",";
    }
    std::cout << v[v.size() - 1] << std::endl;
  }

  return 0;
}

std::ostream_iterator

<algorithm>std::copy()<iterator>std::ostream_iterator を利用する方法がある. 個人的にはこの方法が一番良いと思う.

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>


int
main()
{
  std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  if (!v.empty()) {
    std::copy(
      std::cbegin(v),
      std::prev(std::cend(v)),
      std::ostream_iterator<const decltype(v)::value_type&>(std::cout, ","));
    std::cout << *std::crbegin(v) << std::endl;
  }

  return 0;
}

例のごとく,C++11にはフリー関数の cbegin(), cend(), crbegin() が無い. この程度で別にconstイテレータを取得する必要はないので, cbegin(), cend()begin(), end()を,crbegin()rbegin() メンバ関数を利用する.

#include <algorithm>
#include <iterator>
#include <vector>


int
main()
{
  std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  if (!v.empty()) {
    std::copy(
      std::begin(v),
      std::prev(std::end(v)),
      std::ostream_iterator<const decltype(v)::value_type&>(std::cout, ","));
    std::cout << *v.rbegin() << std::endl;
  }

  return 0;
}

std::experimental::ostream_joiner

標準ライブラリではないが,試験的機能の1つとして std::experimental::ostream_joiner というものが存在する. gccであればバージョン6.1.0以上,clangであればバージョン3.9.1以上であれば利用可能であることは確認した. そして,当然のようにMSVCでは利用できない.

この ostream_joiner には型推論のためのフリー関数テンプレートである std::experimental::make_ostream_joiner() も存在するので,それを利用する.

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

#include <experimental/iterator>


int
main()
{
  std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  std::copy(
    std::cbegin(v),
    std::cend(v),
    std::experimental::make_ostream_joiner(std::cout, ","));
  std::cout << std::endl;

  return 0;
}

Range-based forで何とかする

前回の記事Range クラスを利用する.

#include <iostream>
#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>


template<typename Iterator>
class Range
{
public:
  Range(Iterator&& begin, Iterator&& end) noexcept
    : m_begin(std::forward<Iterator>(begin))
    , m_end(std::forward<Iterator>(end))
  {}

  Iterator
  begin() const noexcept
  {
    return m_begin;
  }

  Iterator
  end() const noexcept
  {
    return m_end;
  }

private:
  const Iterator m_begin;
  const Iterator m_end;
};  // class Range


template<typename Iterator>
static inline Range<Iterator>
makeRange(Iterator&& begin, Iterator&& end) noexcept
{
  return Range<Iterator>{std::forward<Iterator>(begin), std::forward<Iterator>(end)};
}


int
main()
{
  std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  if (!v.empty()) {
    for (const auto& e : makeRange(std::cbegin(v), std::prev(std::cend(v)))) {
      std::cout << e << ",";
    }
    std::cout << *std::crbegin(v) << std::endl;
  }

  return 0;
}