tools: fix nfdc::text::ItemAttributes evaluation order bug

When multiple ItemAttributes::operator() invocations appear on the same
expression, they are not always evaluated left-to-right, causing incorrect
text output.

refs #3864

Change-Id: I4c99c5bc4a76b4becf83f368c9f253c0b254fc05
diff --git a/tests/tools/nfdc/format-helpers.t.cpp b/tests/tools/nfdc/format-helpers.t.cpp
index 99f6966..6184401 100644
--- a/tests/tools/nfdc/format-helpers.t.cpp
+++ b/tests/tools/nfdc/format-helpers.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -71,6 +71,29 @@
   BOOST_CHECK(os.is_equal("1,2,3"));
 }
 
+static void
+printItemAttributes(std::ostream& os, bool wantMultiLine)
+{
+  text::ItemAttributes ia(wantMultiLine, 3);
+  os << ia("id") << 500
+     << ia("uri") << "udp4://192.0.2.1:6363"
+     << ia.end();
+}
+
+BOOST_AUTO_TEST_CASE(ItemAttributesSingleLine)
+{
+  output_test_stream os;
+  printItemAttributes(os, false);
+  BOOST_CHECK(os.is_equal("id=500 uri=udp4://192.0.2.1:6363"));
+}
+
+BOOST_AUTO_TEST_CASE(ItemAttributesMultiLine)
+{
+  output_test_stream os;
+  printItemAttributes(os, true);
+  BOOST_CHECK(os.is_equal(" id=500\nuri=udp4://192.0.2.1:6363\n"));
+}
+
 BOOST_AUTO_TEST_SUITE_END() // Text
 
 BOOST_AUTO_TEST_SUITE_END() // TestFormatHelpers
diff --git a/tools/nfdc/format-helpers.cpp b/tools/nfdc/format-helpers.cpp
index b6b8258..3d851dd 100644
--- a/tools/nfdc/format-helpers.cpp
+++ b/tools/nfdc/format-helpers.cpp
@@ -128,17 +128,7 @@
 ItemAttributes::Attribute
 ItemAttributes::operator()(const std::string& attribute)
 {
-  ++m_count;
-  if (m_wantMultiLine) {
-    return {m_count > 1,
-            {m_maxAttributeWidth - static_cast<int>(attribute.size())},
-            attribute};
-  }
-  else {
-    return {false,
-            {m_count > 1 ? 1 : 0},
-            attribute};
-  }
+  return {*this, attribute};
 }
 
 std::string
@@ -150,13 +140,21 @@
 std::ostream&
 operator<<(std::ostream& os, const ItemAttributes::Attribute& attr)
 {
-  if (attr.wantNewline) {
-    os << '\n';
+  ++attr.ia.m_count;
+  if (attr.ia.m_wantMultiLine) {
+    if (attr.ia.m_count > 1) {
+      os << '\n';
+    }
+    os << Spaces{attr.ia.m_maxAttributeWidth - static_cast<int>(attr.attribute.size())};
   }
-  return os << attr.spaces << attr.attribute << '=';
+  else {
+    if (attr.ia.m_count > 1) {
+      os << ' ';
+    }
+  }
+  return os << attr.attribute << '=';
 }
 
-
 std::string
 formatSeconds(time::seconds d, bool isLong)
 {
diff --git a/tools/nfdc/format-helpers.hpp b/tools/nfdc/format-helpers.hpp
index 4fd2c38..9283855 100644
--- a/tools/nfdc/format-helpers.hpp
+++ b/tools/nfdc/format-helpers.hpp
@@ -140,11 +140,13 @@
 
   struct Attribute
   {
-    bool wantNewline;
-    Spaces spaces;
+    ItemAttributes& ia;
     std::string attribute;
   };
 
+  /** \note Caller must ensure ItemAttributes object is alive until after all Attribute objects are
+   *        destructed.
+   */
   Attribute
   operator()(const std::string& attribute);
 
@@ -155,6 +157,8 @@
   bool m_wantMultiLine;
   int m_maxAttributeWidth;
   int m_count;
+
+  friend std::ostream& operator<<(std::ostream& os, const ItemAttributes::Attribute& attr);
 };
 
 std::ostream&