util: backport std::experimental::ostream_joiner

Change-Id: I4d12269ac41f07d224772abc0e50039be519fdcc
Refs: #3962
diff --git a/src/encoding/nfd-constants.cpp b/src/encoding/nfd-constants.cpp
index 2b9f8ff..b502a9c 100644
--- a/src/encoding/nfd-constants.cpp
+++ b/src/encoding/nfd-constants.cpp
@@ -119,36 +119,25 @@
     return os << "none";
   }
 
-  bool isFirst = true;
-  auto printToken = [&os, &isFirst] (const std::string& token) {
-    if (isFirst) {
-      isFirst = false;
-    }
-    else {
-      os << '|';
-    }
-    os << token;
-  };
-
   static const std::map<RouteFlags, std::string> knownBits = {
     {ROUTE_FLAG_CHILD_INHERIT, "child-inherit"},
-    {ROUTE_FLAG_CAPTURE, "capture"}};
+    {ROUTE_FLAG_CAPTURE, "capture"}
+  };
+
+  auto join = make_ostream_joiner(os, '|');
   for (const auto& pair : knownBits) {
     RouteFlags bit = ROUTE_FLAGS_NONE;
     std::string token;
     std::tie(bit, token) = pair;
 
     if ((routeFlags & bit) != 0) {
-      printToken(token);
+      join = token;
       routeFlags = static_cast<RouteFlags>(routeFlags & ~bit);
     }
   }
 
   if (routeFlags != ROUTE_FLAGS_NONE) {
-    if (!isFirst) {
-      os << '|';
-    }
-    os << AsHex{routeFlags};
+    join = AsHex{routeFlags};
   }
 
   return os;
diff --git a/src/exclude.cpp b/src/exclude.cpp
index 7007daf..0f339e0 100644
--- a/src/exclude.cpp
+++ b/src/exclude.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -24,7 +24,7 @@
 #include "exclude.hpp"
 #include "encoding/block-helpers.hpp"
 
-#include <boost/range/adaptors.hpp>
+#include <boost/range/adaptor/reversed.hpp>
 
 namespace ndn {
 
@@ -317,19 +317,13 @@
 std::ostream&
 operator<<(std::ostream& os, const Exclude& exclude)
 {
-  bool isFirst = true;
+  auto join = make_ostream_joiner(os, ',');
   for (const Exclude::Entry& entry : exclude.m_entries | boost::adaptors::reversed) {
     if (!entry.first.isNegInf) {
-      if (!isFirst)
-        os << ",";
-      entry.first.component.toUri(os);
-      isFirst = false;
+      join = entry.first.component;
     }
     if (entry.second) {
-      if (!isFirst)
-        os << ",";
-      os << "*";
-      isFirst = false;
+      join = '*';
     }
   }
   return os;
diff --git a/src/mgmt/nfd/fib-entry.cpp b/src/mgmt/nfd/fib-entry.cpp
index e3633aa..db3b69f 100644
--- a/src/mgmt/nfd/fib-entry.cpp
+++ b/src/mgmt/nfd/fib-entry.cpp
@@ -269,13 +269,9 @@
   os << "FibEntry(Prefix: " << entry.getPrefix() << ",\n"
      << "         NextHops: [";
 
-  bool first = true;
-  for (const auto& nh : entry.getNextHopRecords()) {
-    if (!first)
-      os << ",\n                    ";
-    first = false;
-    os << nh;
-  }
+  std::copy(entry.getNextHopRecords().begin(), entry.getNextHopRecords().end(),
+            make_ostream_joiner(os, ",\n                    "));
+
   os << "]\n";
 
   return os << "         )";
diff --git a/src/mgmt/nfd/rib-entry.cpp b/src/mgmt/nfd/rib-entry.cpp
index c7ec47a..315b4bd 100644
--- a/src/mgmt/nfd/rib-entry.cpp
+++ b/src/mgmt/nfd/rib-entry.cpp
@@ -348,13 +348,9 @@
   os << "RibEntry(Prefix: " << entry.getName() << ",\n"
      << "         Routes: [";
 
-  bool isFirst = true;
-  for (const auto& route : entry.getRoutes()) {
-    if (!isFirst)
-      os << ",\n                  ";
-    isFirst = false;
-    os << route;
-  }
+  std::copy(entry.getRoutes().begin(), entry.getRoutes().end(),
+            make_ostream_joiner(os, ",\n                  "));
+
   os << "]\n";
 
   return os << "         )";
diff --git a/src/util/backports-ostream-joiner.hpp b/src/util/backports-ostream-joiner.hpp
new file mode 100644
index 0000000..384f24a
--- /dev/null
+++ b/src/util/backports-ostream-joiner.hpp
@@ -0,0 +1,129 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+/** \file
+ *  \brief Backport of ostream_joiner from the Library Fundamentals v2 TS
+ *  \sa http://en.cppreference.com/w/cpp/experimental/ostream_joiner
+ */
+
+#ifndef NDN_UTIL_BACKPORTS_OSTREAM_JOINER_HPP
+#define NDN_UTIL_BACKPORTS_OSTREAM_JOINER_HPP
+
+#include "../common.hpp"
+
+#if __cplusplus >= 201402L
+#  ifdef __has_include
+#    if __has_include(<experimental/iterator>)
+#      include <experimental/iterator>
+#      if __cpp_lib_experimental_ostream_joiner >= 201411
+#        define NDN_CXX_HAVE_EXPERIMENTAL_OSTREAM_JOINER
+#      endif
+#    endif
+#  endif
+#endif
+
+#ifdef NDN_CXX_HAVE_EXPERIMENTAL_OSTREAM_JOINER
+
+namespace ndn {
+using std::experimental::ostream_joiner;
+using std::experimental::make_ostream_joiner;
+} // namespace ndn
+
+#else
+
+#include <iterator>
+
+namespace ndn {
+
+template<typename DelimT,
+         typename CharT = char,
+         typename Traits = std::char_traits<CharT>>
+class ostream_joiner
+{
+public:
+  typedef CharT char_type;
+  typedef Traits traits_type;
+  typedef std::basic_ostream<CharT, Traits> ostream_type;
+  typedef std::output_iterator_tag iterator_category;
+  typedef void value_type;
+  typedef void difference_type;
+  typedef void pointer;
+  typedef void reference;
+
+  ostream_joiner(ostream_type& os, const DelimT& delimiter)
+  noexcept(std::is_nothrow_copy_constructible<DelimT>::value)
+    : m_os(std::addressof(os)), m_delim(delimiter)
+  {
+  }
+
+  ostream_joiner(ostream_type& os, DelimT&& delimiter)
+  noexcept(std::is_nothrow_move_constructible<DelimT>::value)
+    : m_os(std::addressof(os)), m_delim(std::move(delimiter))
+  {
+  }
+
+  template<typename T>
+  ostream_joiner&
+  operator=(const T& value)
+  {
+    if (!m_isFirst) {
+      *m_os << m_delim;
+    }
+    m_isFirst = false;
+    *m_os << value;
+    return *this;
+  }
+
+  ostream_joiner&
+  operator*() noexcept
+  {
+    return *this;
+  }
+
+  ostream_joiner&
+  operator++() noexcept
+  {
+    return *this;
+  }
+
+  ostream_joiner&
+  operator++(int) noexcept
+  {
+    return *this;
+  }
+
+private:
+  ostream_type* m_os;
+  DelimT m_delim;
+  bool m_isFirst = true;
+};
+
+template<typename CharT, typename Traits, typename DelimT>
+inline ostream_joiner<typename std::decay<DelimT>::type, CharT, Traits>
+make_ostream_joiner(std::basic_ostream<CharT, Traits>& os, DelimT&& delimiter)
+{
+  return {os, std::forward<DelimT>(delimiter)};
+}
+
+} // namespace ndn
+
+#endif // NDN_CXX_HAVE_EXPERIMENTAL_OSTREAM_JOINER
+#endif // NDN_UTIL_BACKPORTS_OSTREAM_JOINER_HPP
diff --git a/src/util/backports.hpp b/src/util/backports.hpp
index acb03e6..1d0a92a 100644
--- a/src/util/backports.hpp
+++ b/src/util/backports.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2015-2016 Regents of the University of California.
+ * Copyright (c) 2015-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -28,11 +28,9 @@
 #include <boost/lexical_cast.hpp>
 #endif
 
-#include <algorithm>
-
 namespace ndn {
 
-#if __cpp_lib_make_unique
+#if __cpp_lib_make_unique >= 201304
 using std::make_unique;
 #else
 template<typename T, typename ...Args>
@@ -75,5 +73,6 @@
 } // namespace ndn
 
 #include "backports-optional.hpp"
+#include "backports-ostream-joiner.hpp"
 
 #endif // NDN_UTIL_BACKPORTS_HPP
diff --git a/tests/unit-tests/util/backports.t.cpp b/tests/unit-tests/util/backports.t.cpp
index 6d8dfd2..21afb0d 100644
--- a/tests/unit-tests/util/backports.t.cpp
+++ b/tests/unit-tests/util/backports.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -22,6 +22,7 @@
 #include "util/backports.hpp"
 
 #include "boost-test.hpp"
+#include <numeric>
 
 namespace ndn {
 namespace tests {
@@ -267,6 +268,40 @@
 
 BOOST_AUTO_TEST_SUITE_END() // Optional
 
+BOOST_AUTO_TEST_CASE(OstreamJoiner)
+{
+  boost::test_tools::output_test_stream os;
+
+  auto joiner1 = ostream_joiner<char>(os, ' ');
+  auto joiner2 = make_ostream_joiner(os, ' ');
+  static_assert(std::is_same<decltype(joiner1), decltype(joiner2)>::value, "");
+
+  std::vector<int> v(5);
+  std::iota(v.begin(), v.end(), 1);
+  std::copy(v.begin(), v.end(), joiner2);
+  BOOST_CHECK(os.is_equal("1 2 3 4 5"));
+
+  auto joiner3 = make_ostream_joiner(os, "...");
+  std::copy(v.begin(), v.end(), joiner3);
+  BOOST_CHECK(os.is_equal("1...2...3...4...5"));
+
+  joiner3 = "one";
+  BOOST_CHECK(os.is_equal("one"));
+  joiner3 = "two";
+  BOOST_CHECK(os.is_equal("...two"));
+  ++joiner3 = "three";
+  BOOST_CHECK(os.is_equal("...three"));
+  joiner3++ = "four";
+  BOOST_CHECK(os.is_equal("...four"));
+
+  std::copy(v.begin(), v.end(), make_ostream_joiner(os, ""));
+  BOOST_CHECK(os.is_equal("12345"));
+
+  std::string delimiter("_");
+  std::copy(v.begin(), v.end(), make_ostream_joiner(os, delimiter));
+  BOOST_CHECK(os.is_equal("1_2_3_4_5"));
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestBackports
 BOOST_AUTO_TEST_SUITE_END() // Util