はじめに
C++において,たまに std:cout
や std:ofstream
, std::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; }