[mlpack-git] master: Refactor to support input and output parameters. (3fc0fbf)

gitdub at mlpack.org gitdub at mlpack.org
Tue Jul 12 14:55:11 EDT 2016


Repository : https://github.com/mlpack/mlpack
On branch  : master
Link       : https://github.com/mlpack/mlpack/compare/59bc0b32630a3ad786706993f4d5e8b087f1c702...0d9a0e263a32b99d8dcf5d2723b3b92c67e669fc

>---------------------------------------------------------------

commit 3fc0fbfba3aa1dfe1911110135bcda071b583666
Author: Ryan Curtin <ryan at ratml.org>
Date:   Tue Jul 12 14:55:11 2016 -0400

    Refactor to support input and output parameters.


>---------------------------------------------------------------

3fc0fbfba3aa1dfe1911110135bcda071b583666
 src/mlpack/core/util/cli.cpp         |  64 ++++--
 src/mlpack/core/util/cli.hpp         | 346 ++--------------------------
 src/mlpack/core/util/cli_impl.hpp    |  20 +-
 src/mlpack/core/util/option.hpp      |   8 +-
 src/mlpack/core/util/option_impl.hpp |  11 +-
 src/mlpack/core/util/param.hpp       | 426 +++++++++++++++++++++++++++++++++++
 src/mlpack/tests/cli_test.cpp        |   2 +-
 7 files changed, 508 insertions(+), 369 deletions(-)

diff --git a/src/mlpack/core/util/cli.cpp b/src/mlpack/core/util/cli.cpp
index 0eaf543..1f6dd29 100644
--- a/src/mlpack/core/util/cli.cpp
+++ b/src/mlpack/core/util/cli.cpp
@@ -99,11 +99,14 @@ CLI::~CLI()
  * @param description Short string description of the parameter.
  * @param alias An alias for the parameter.
  * @param required Indicates if parameter must be set on command line.
+ * @param input If true, the parameter is an input parameter (not an output
+ *      parameter).
  */
 void CLI::Add(const std::string& path,
-             const std::string& description,
-             const std::string& alias,
-             bool required)
+              const std::string& description,
+              const std::string& alias,
+              const bool required,
+              const bool input)
 {
   po::options_description& desc = CLI::GetSingleton().desc;
 
@@ -132,6 +135,13 @@ void CLI::Add(const std::string& path,
   if (required)
     GetSingleton().requiredOptions.push_front(path);
 
+  // Depending on whether the option is input or output, add it to the list of
+  // input or output options.
+  if (input)
+    GetSingleton().inputOptions.push_front(path);
+  else
+    GetSingleton().outputOptions.push_front(path);
+
   return;
 }
 
@@ -600,7 +610,7 @@ void CLI::PrintHelp(const std::string& param)
   else
     std::cout << "[undocumented program]" << std::endl << std::endl;
 
-  for (size_t pass = 0; pass < 2; ++pass)
+  for (size_t pass = 0; pass < 4; ++pass)
   {
     bool printedHeader = false;
 
@@ -613,29 +623,40 @@ void CLI::PrintHelp(const std::string& param)
       std::string alias = AliasReverseLookup(key);
       alias = alias.length() ? " (-" + alias + ")" : alias;
 
-      // Is the option required or not?
-      bool required = false;
-      std::list<std::string>::iterator iter;
-      std::list<std::string>& rOpt = GetSingleton().requiredOptions;
-      for (iter = rOpt.begin(); iter != rOpt.end(); ++iter)
-        if ((*iter) == key)
-          required = true;
-
-      if ((pass == 0) && !required)
-        continue; // Don't print this one.
-      if ((pass == 1) && required)
-        continue; // Don't print this one.
+      // Is the option required or not?  And is it an input option or not?
+      const std::list<std::string>& requiredOptions =
+          GetSingleton().requiredOptions;
+      const std::list<std::string>& inputOptions = GetSingleton().inputOptions;
+
+      const bool required = (std::find(std::begin(requiredOptions),
+          std::end(requiredOptions), key) != std::end(requiredOptions));
+      const bool input = (std::find(std::begin(inputOptions),
+          std::end(inputOptions), key) != std::end(inputOptions));
+
+      // Filter un-printed options.
+      if ((pass == 0) && !(required && input)) // Required input options only.
+        continue;
+      if ((pass == 1) && !(required && !input)) // Required output options only.
+        continue;
+      if ((pass == 2) && !(!required && input)) // Optional input options only.
+        continue;
+      if ((pass == 3) && (required || input)) // Optional output options only.
+        continue;
 
       if (!printedHeader)
       {
         printedHeader = true;
         if (pass == 0)
-          std::cout << "Required options:" << std::endl << std::endl;
-        else
-          std::cout << "Options: " << std::endl << std::endl;
+          std::cout << "Required input options:" << std::endl << std::endl;
+        else if (pass == 1)
+          std::cout << "Required output options:" << std::endl << std::endl;
+        else if (pass == 2)
+          std::cout << "Optional input options: " << std::endl << std::endl;
+        else if (pass == 3)
+          std::cout << "Optional output options: " << std::endl << std::endl;
       }
 
-      if (pass == 1) // Append default value to description.
+      if (pass >= 2) // Append default value to description.
       {
         desc += "  Default value ";
         std::stringstream tmp;
@@ -679,7 +700,6 @@ void CLI::PrintHelp(const std::string& param)
     }
 
     std::cout << std::endl;
-
   }
 
   // Helpful information at the bottom of the help output, to point the user to
@@ -751,7 +771,7 @@ void CLI::UpdateGmap()
 
 // Add help parameter.
 PARAM_FLAG("help", "Default help info.", "h");
-PARAM_STRING("info", "Get help on a specific module or option.", "", "");
+PARAM_STRING_IN("info", "Get help on a specific module or option.", "", "");
 PARAM_FLAG("verbose", "Display informational messages and the full list of "
     "parameters and timers at the end of execution.", "v");
 PARAM_FLAG("version", "Display the version of mlpack.", "V");
diff --git a/src/mlpack/core/util/cli.hpp b/src/mlpack/core/util/cli.hpp
index f6ec7dc..56c1202 100644
--- a/src/mlpack/core/util/cli.hpp
+++ b/src/mlpack/core/util/cli.hpp
@@ -20,336 +20,7 @@
 #include "timers.hpp"
 #include "cli_deleter.hpp" // To make sure we can delete the singleton.
 #include "version.hpp"
-
-/**
- * Document an executable.  Only one instance of this macro should be
- * present in your program!  Therefore, use it in the main.cpp
- * (or corresponding executable) in your program.
- *
- * @see mlpack::CLI, PARAM_FLAG(), PARAM_INT(), PARAM_DOUBLE(), PARAM_STRING(),
- * PARAM_VECTOR(), PARAM_INT_REQ(), PARAM_DOUBLE_REQ(), PARAM_STRING_REQ(),
- * PARAM_VECTOR_REQ().
- *
- * @param NAME Short string representing the name of the program.
- * @param DESC Long string describing what the program does and possibly a
- *     simple usage example.  Newlines should not be used here; this is taken
- *     care of by CLI (however, you can explicitly specify newlines to denote
- *     new paragraphs).
- */
-#define PROGRAM_INFO(NAME, DESC) static mlpack::util::ProgramDoc \
-    io_programdoc_dummy_object = mlpack::util::ProgramDoc(NAME, DESC);
-
-/**
- * Define a flag parameter.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_FLAG(ID, DESC, ALIAS) \
-    PARAM_FLAG_INTERNAL(ID, DESC, ALIAS);
-
-/**
- * Define an integer parameter.
- *
- * The parameter can then be specified on the command line with
- * --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- * @param DEF Default value of the parameter.
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_INT(ID, DESC, ALIAS, DEF) \
-    PARAM(int, ID, DESC, ALIAS, DEF, false)
-
-/**
- * Define a floating-point parameter.  You should use PARAM_DOUBLE instead.
- *
- * The parameter can then be specified on the command line with
- * --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- * @param DEF Default value of the parameter.
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_FLOAT(ID, DESC, ALIAS, DEF) \
-    PARAM(float, ID, DESC, ALIAS, DEF, false)
-
-/**
- * Define a double parameter.
- *
- * The parameter can then be specified on the command line with
- * --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- * @param DEF Default value of the parameter.
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_DOUBLE(ID, DESC, ALIAS, DEF) \
-    PARAM(double, ID, DESC, ALIAS, DEF, false)
-
-/**
- * Define a string parameter.
- *
- * The parameter can then be specified on the command line with
- * --ID=value. If ALIAS is equal to DEF_MOD (which is set using the
- * PROGRAM_INFO() macro), the parameter can be specified with just --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- * @param DEF Default value of the parameter.
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_STRING(ID, DESC, ALIAS, DEF) \
-    PARAM(std::string, ID, DESC, ALIAS, DEF, false)
-
-/**
- * Define a vector parameter.
- *
- * The parameter can then be specified on the command line with
- * --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- * @param DEF Default value of the parameter.
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_VECTOR(T, ID, DESC, ALIAS) \
-    PARAM(std::vector<T>, ID, DESC, ALIAS, std::vector<T>(), false)
-
-// A required flag doesn't make sense and isn't given here.
-
-/**
- * Define a required integer parameter.
- *
- * The parameter must then be specified on the command line with
- * --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_INT_REQ(ID, DESC, ALIAS) PARAM(int, ID, DESC, ALIAS, 0, true)
-
-/**
- * Define a required floating-point parameter.  You should probably use a double
- * instead.
- *
- * The parameter must then be specified on the command line with
- * --ID=value. If ALIAS is equal to DEF_MOD (which is set using the
- * PROGRAM_INFO() macro), the parameter can be specified with just --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_FLOAT_REQ(ID, DESC, ALIAS) PARAM(float, ID, DESC, ALIAS, 0.0f, \
-    true)
-
-/**
- * Define a required double parameter.
- *
- * The parameter must then be specified on the command line with
- * --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_DOUBLE_REQ(ID, DESC, ALIAS) PARAM(double, ID, DESC, ALIAS, \
-    0.0f, true)
-
-/**
- * Define a required string parameter.
- *
- * The parameter must then be specified on the command line with
- * --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_STRING_REQ(ID, DESC, ALIAS) PARAM(std::string, ID, DESC, \
-    ALIAS, "", true);
-
-/**
- * Define a required vector parameter.
- *
- * The parameter must then be specified on the command line with
- * --ID=value.
- *
- * @param ID Name of the parameter.
- * @param DESC Quick description of the parameter (1-2 sentences).
- * @param ALIAS An alias for the parameter (one letter).
- *
- * @see mlpack::CLI, PROGRAM_INFO()
- *
- * @bug
- * The __COUNTER__ variable is used in most cases to guarantee a unique global
- * identifier for options declared using the PARAM_*() macros. However, not all
- * compilers have this support--most notably, gcc < 4.3. In that case, the
- * __LINE__ macro is used as an attempt to get a unique global identifier, but
- * collisions are still possible, and they produce bizarre error messages.  See
- * https://github.com/mlpack/mlpack/issues/100 for more information.
- */
-#define PARAM_VECTOR_REQ(T, ID, DESC, ALIAS) PARAM(std::vector<T>, ID, DESC, \
-    ALIAS, std::vector<T>(), true);
-
-/**
- * @cond
- * Don't document internal macros.
- */
-
-// These are ugly, but necessary utility functions we must use to generate a
-// unique identifier inside of the PARAM() module.
-#define JOIN(x, y) JOIN_AGAIN(x, y)
-#define JOIN_AGAIN(x, y) x ## y
-/** @endcond */
-
-/**
- * Define an input parameter.  Don't use this function; use the other ones above
- * that call it.  Note that we are using the __LINE__ macro for naming these
- * actual parameters when __COUNTER__ does not exist, which is a bit of an ugly
- * hack... but this is the preprocessor, after all.  We don't have much choice
- * other than ugliness.
- *
- * @param T Type of the parameter.
- * @param ID Name of the parameter.
- * @param DESC Description of the parameter (1-2 sentences).
- * @param ALIAS Alias for this parameter (one letter).
- * @param DEF Default value of the parameter.
- * @param REQ Whether or not parameter is required (boolean value).
- */
-#ifdef __COUNTER__
-  #define PARAM(T, ID, DESC, ALIAS, DEF, REQ) static mlpack::util::Option<T> \
-      JOIN(io_option_dummy_object_, __COUNTER__) \
-      (false, DEF, ID, DESC, ALIAS, REQ);
-
-  /** @cond Don't document internal macros. */
-  #define PARAM_FLAG_INTERNAL(ID, DESC, ALIAS) static \
-      mlpack::util::Option<bool> JOIN(__io_option_flag_object_, __COUNTER__) \
-      (ID, DESC, ALIAS);
-  /** @endcond */
-
-#else
-  // We have to do some really bizarre stuff since __COUNTER__ isn't defined.  I
-  // don't think we can absolutely guarantee success, but it should be "good
-  // enough".  We use the __LINE__ macro and the type of the parameter to try
-  // and get a good guess at something unique.
-  #define PARAM(T, ID, DESC, ALIAS, DEF, REQ) static mlpack::util::Option<T> \
-      JOIN(JOIN(io_option_dummy_object_, __LINE__), opt) (false, DEF, ID, \
-      DESC, ALIAS, REQ);
-
-  /** @cond Don't document internal macros. */
-  #define PARAM_FLAG_INTERNAL(ID, DESC, ALIAS) static \
-      mlpack::util::Option<bool> JOIN(__io_option_flag_object_, __LINE__) \
-      (ID, DESC, ALIAS);
-  /** @endcond */
-
-#endif
+#include "param.hpp"
 
 /**
  * The TYPENAME macro is used internally to convert a type into a string.
@@ -384,7 +55,7 @@ struct ParamData
   boost::any value;
   //! True if this parameter was passed in via command line or file.
   bool wasPassed;
-  //! True if the wasPassed value should not be ignored
+  //! True if the wasPassed value should not be ignored.
   bool isFlag;
 };
 
@@ -526,11 +197,13 @@ class CLI
    * @param alias An alias for the parameter, defaults to "" which is no alias.
    *    ("").
    * @param required Indicates if parameter must be set on command line.
+   * @param input If true, the parameter is an input (not output) parameter.
    */
   static void Add(const std::string& path,
                   const std::string& description,
                   const std::string& alias = "",
-                  bool required = false);
+                  const bool required = false,
+                  const bool input = true);
 
   /**
    * Adds a parameter to the hierarchy; use the PARAM_*() macros instead of this
@@ -542,12 +215,14 @@ class CLI
    * @param description Short string description of the parameter.
    * @param alias An alias for the parameter, defaults to "" which is no alias.
    * @param required Indicates if parameter must be set on command line.
+   * @param input If true, the parameter is an input (not output) parameter.
    */
   template<class T>
   static void Add(const std::string& identifier,
                   const std::string& description,
                   const std::string& alias = "",
-                  bool required = false);
+                  const bool required = false,
+                  const bool input = true);
 
   /**
    * Adds a flag parameter to the hierarchy; use PARAM_FLAG() instead of this.
@@ -676,6 +351,11 @@ class CLI
   //! Pathnames of required options.
   std::list<std::string> requiredOptions;
 
+  //! Pathnames of input options.
+  std::list<std::string> inputOptions;
+  //! Pathnames of output options.
+  std::list<std::string> outputOptions;
+
   //! Map of global values.
   typedef std::map<std::string, ParamData> gmap_t;
   gmap_t globalValues;
diff --git a/src/mlpack/core/util/cli_impl.hpp b/src/mlpack/core/util/cli_impl.hpp
index 72f4be2..4592650 100644
--- a/src/mlpack/core/util/cli_impl.hpp
+++ b/src/mlpack/core/util/cli_impl.hpp
@@ -22,16 +22,19 @@ namespace mlpack {
  * @tparam T The type of the parameter.
  * @param identifier The name of the parameter, eg foo in bar/foo.
  * @param description A string description of the parameter.
- * @param parent The name of the parent of the parameter,
- *   eg bar/foo in bar/foo/buzz.
- * @param required If required, the program will refuse to run
- *   unless the parameter is specified.
+ * @param parent The name of the parent of the parameter, e.g. bar/foo in
+ *     bar/foo/buzz.
+ * @param required If required, the program will refuse to run unless the
+ *     parameter is specified.
+ * @param input If true, the parameter is an input parameter (not an output
+ *     parameter).
  */
 template<typename T>
 void CLI::Add(const std::string& path,
               const std::string& description,
               const std::string& alias,
-              bool required)
+              const bool required,
+              const bool input)
 {
 
   po::options_description& desc = CLI::GetSingleton().desc;
@@ -61,6 +64,13 @@ void CLI::Add(const std::string& path,
   // If the option is required, add it to the required options list.
   if (required)
     GetSingleton().requiredOptions.push_front(path);
+
+  // Depending on whether or not the option is input or output, add it to the
+  // appropriate list.
+  if (input)
+    GetSingleton().inputOptions.push_front(path);
+  else
+    GetSingleton().outputOptions.push_front(path);
 }
 
 // We specialize this in cli.cpp.
diff --git a/src/mlpack/core/util/option.hpp b/src/mlpack/core/util/option.hpp
index cf64079..1efef14 100644
--- a/src/mlpack/core/util/option.hpp
+++ b/src/mlpack/core/util/option.hpp
@@ -41,13 +41,15 @@ class Option
    * @param parent Full pathname of the parent module that "owns" this option.
    *      The default is the root node (an empty string).
    * @param required Whether or not the option is required at runtime.
+   * @param input Whether or not the option is an input option.
    */
-  Option(bool ignoreTemplate,
-         N defaultValue,
+  Option(const bool ignoreTemplate,
+         const N defaultValue,
          const std::string& identifier,
          const std::string& description,
          const std::string& parent = std::string(""),
-         bool required = false);
+         const bool required = false,
+         const bool input = true);
 
   /**
    * Constructs an Option object.  When constructed, it will register a flag
diff --git a/src/mlpack/core/util/option_impl.hpp b/src/mlpack/core/util/option_impl.hpp
index 6684c1a..56a8128 100644
--- a/src/mlpack/core/util/option_impl.hpp
+++ b/src/mlpack/core/util/option_impl.hpp
@@ -17,20 +17,21 @@ namespace util {
  * Registers a parameter with CLI.
  */
 template<typename N>
-Option<N>::Option(bool ignoreTemplate,
-                  N defaultValue,
+Option<N>::Option(const bool ignoreTemplate,
+                  const N defaultValue,
                   const std::string& identifier,
                   const std::string& description,
                   const std::string& alias,
-                  bool required)
+                  const bool required,
+                  const bool input)
 {
   if (ignoreTemplate)
   {
-    CLI::Add(identifier, description, alias, required);
+    CLI::Add(identifier, description, alias, required, input);
   }
   else
   {
-    CLI::Add<N>(identifier, description, alias, required);
+    CLI::Add<N>(identifier, description, alias, required, input);
     CLI::GetParam<N>(identifier) = defaultValue;
   }
 }
diff --git a/src/mlpack/core/util/param.hpp b/src/mlpack/core/util/param.hpp
new file mode 100644
index 0000000..7756908
--- /dev/null
+++ b/src/mlpack/core/util/param.hpp
@@ -0,0 +1,426 @@
+/**
+ * @file param.hpp
+ * @author Matthew Amidon
+ * @author Ryan Curtin
+ *
+ * Definition of PARAM_*_IN() and PARAM_*_OUT() macros, as well as the
+ * PROGRAM_INFO() macro, which are used to define input and output parameters of
+ * command-line programs and bindings to other languages.
+ */
+#ifndef MLPACK_CORE_UTIL_PARAM_HPP
+#define MLPACK_CORE_UTIL_PARAM_HPP
+
+/**
+ * Document an executable.  Only one instance of this macro should be
+ * present in your program!  Therefore, use it in the main.cpp
+ * (or corresponding executable) in your program.
+ *
+ * @see mlpack::CLI, PARAM_FLAG(), PARAM_INT_IN(), PARAM_DOUBLE_IN(),
+ * PARAM_STRING_IN(), PARAM_VECTOR_IN(), PARAM_INT_OUT(), PARAM_DOUBLE_OUT(),
+ * PARAM_VECTOR_OUT(), PARAM_INT_IN_REQ(), PARAM_DOUBLE_IN_REQ(),
+ * PARAM_STRING_IN_REQ(), PARAM_VECTOR_IN_REQ(), PARAM_INT_OUT_REQ(),
+ * PARAM_DOUBLE_OUT_REQ(), PARAM_VECTOR_OUT_REQ(), PARAM_STRING_OUT_REQ().
+ *
+ * @param NAME Short string representing the name of the program.
+ * @param DESC Long string describing what the program does and possibly a
+ *     simple usage example.  Newlines should not be used here; this is taken
+ *     care of by CLI (however, you can explicitly specify newlines to denote
+ *     new paragraphs).
+ */
+#define PROGRAM_INFO(NAME, DESC) static mlpack::util::ProgramDoc \
+    cli_programdoc_dummy_object = mlpack::util::ProgramDoc(NAME, DESC);
+
+/**
+ * Define a flag parameter.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_FLAG(ID, DESC, ALIAS) \
+    PARAM_FLAG_INTERNAL(ID, DESC, ALIAS);
+
+/**
+ * Define an integer input parameter.
+ *
+ * The parameter can then be specified on the command line with
+ * --ID=value.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ * @param DEF Default value of the parameter.
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_INT_IN(ID, DESC, ALIAS, DEF) \
+    PARAM_IN(int, ID, DESC, ALIAS, DEF, false)
+
+/**
+ * Define an integer output parameter.  This parameter will be printed on stdout
+ * at the end of the program; for instance, if the parameter name is "number"
+ * and the value is 5, the output on stdout would be of the following form:
+ *
+ * @code
+ * number: 5
+ * @endcode
+ *
+ * If the parameter is not set by the end of the program, a fatal runtime error
+ * will be issued.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_INT_OUT(ID, DESC) \
+    PARAM_IN(int, ID, DESC, "", 0, false)
+
+/**
+ * Define a double input parameter.
+ *
+ * The parameter can then be specified on the command line with
+ * --ID=value.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ * @param DEF Default value of the parameter.
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_DOUBLE_IN(ID, DESC, ALIAS, DEF) \
+    PARAM_IN(double, ID, DESC, ALIAS, DEF, false)
+
+/**
+ * Define a double output parameter.  This parameter will be printed on stdout
+ * at the end of the program; for instance, if the parameter name is "number"
+ * and the value is 5.012, the output on stdout would be of the following form:
+ *
+ * @code
+ * number: 5.012
+ * @endcode
+ *
+ * If the parameter is not set by the end of the program, a fatal runtime error
+ * will be issued.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_DOUBLE_OUT(ID, DESC) \
+    PARAM_OUT(double, ID, DESC, "", 0.0d, false)
+
+/**
+ * Define a string input parameter.
+ *
+ * The parameter can then be specified on the command line with
+ * --ID=value. If ALIAS is equal to DEF_MOD (which is set using the
+ * PROGRAM_INFO() macro), the parameter can be specified with just --ID=value.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ * @param DEF Default value of the parameter.
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_STRING_IN(ID, DESC, ALIAS, DEF) \
+    PARAM_IN(std::string, ID, DESC, ALIAS, DEF, false)
+
+/**
+ * Define a string output parameter.
+ *
+ * If the parameter name does not end in "_file" (i.e. "output_file",
+ * "predictions_file", etc.), then the string will be printed to stdout at the
+ * end of the program.  For instance, if there was a string output parameter
+ * called "something" with value "hello", at the end of the program the output
+ * would be of the following form:
+ *
+ * @code
+ * something: "hello"
+ * @endcode
+ *
+ * If the parameter is not set by the end of the program, a fatal runtime error
+ * will be issued.
+ *
+ * An alias is still allowed for string output parameters, because if the
+ * parameter name ends in "_file", then the user must be able to specify it as
+ * input.  The default value will always be the empty string.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_STRING_OUT(ID, DESC, ALIAS) \
+    PARAM_OUT(std::string, ID, DESC, ALIAS, "", false)
+
+/**
+ * Define a vector input parameter.
+ *
+ * The parameter can then be specified on the command line with
+ * --ID=value1,value2,value3.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ * @param DEF Default value of the parameter.
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_VECTOR_IN(T, ID, DESC, ALIAS) \
+    PARAM_IN(std::vector<T>, ID, DESC, ALIAS, std::vector<T>(), false)
+
+/**
+ * Define a vector output parameter.  This vector will be printed on stdout at
+ * the end of the program; for instance, if the parameter name is "vector" and
+ * the vector holds the array { 1, 2, 3, 4 }, the output on stdout would be of
+ * the following form:
+ *
+ * @code
+ * vector: 1, 2, 3, 4
+ * @endcode
+ *
+ * If the parameter is not set by the end of the program, a fatal runtime error
+ * will be issued.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_VECTOR_OUT(T, ID) \
+    PARAM_OUT(std::vector<T>, ID, DESC, "", std::vector<T>(), false)
+
+/**
+ * Define a required integer input parameter.
+ *
+ * The parameter must then be specified on the command line with --ID=value.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_INT_IN_REQ(ID, DESC, ALIAS) \
+    PARAM_IN(int, ID, DESC, ALIAS, 0, true)
+
+/**
+ * Define a required double parameter.
+ *
+ * The parameter must then be specified on the command line with --ID=value.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_DOUBLE_IN_REQ(ID, DESC, ALIAS) \
+    PARAM_IN(double, ID, DESC, ALIAS, 0.0d, true)
+
+/**
+ * Define a required string parameter.
+ *
+ * The parameter must then be specified on the command line with --ID=value.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_STRING_IN_REQ(ID, DESC, ALIAS) \
+    PARAM_IN(std::string, ID, DESC, ALIAS, "", true)
+
+/**
+ * Define a required vector parameter.
+ *
+ * The parameter must then be specified on the command line with
+ * --ID=value1,value2,value3.
+ *
+ * @param ID Name of the parameter.
+ * @param DESC Quick description of the parameter (1-2 sentences).
+ * @param ALIAS An alias for the parameter (one letter).
+ *
+ * @see mlpack::CLI, PROGRAM_INFO()
+ *
+ * @bug
+ * The __COUNTER__ variable is used in most cases to guarantee a unique global
+ * identifier for options declared using the PARAM_*() macros. However, not all
+ * compilers have this support--most notably, gcc < 4.3. In that case, the
+ * __LINE__ macro is used as an attempt to get a unique global identifier, but
+ * collisions are still possible, and they produce bizarre error messages.  See
+ * https://github.com/mlpack/mlpack/issues/100 for more information.
+ */
+#define PARAM_VECTOR_IN_REQ(T, ID, DESC, ALIAS) \
+    PARAM_IN(std::vector<T>, ID, DESC, ALIAS, std::vector<T>(), true);
+
+/**
+ * @cond
+ * Don't document internal macros.
+ */
+
+// These are ugly, but necessary utility functions we must use to generate a
+// unique identifier inside of the PARAM() module.
+#define JOIN(x, y) JOIN_AGAIN(x, y)
+#define JOIN_AGAIN(x, y) x ## y
+/** @endcond */
+
+/**
+ * Define an input parameter.  Don't use this function; use the other ones above
+ * that call it.  Note that we are using the __LINE__ macro for naming these
+ * actual parameters when __COUNTER__ does not exist, which is a bit of an ugly
+ * hack... but this is the preprocessor, after all.  We don't have much choice
+ * other than ugliness.
+ *
+ * @param T Type of the parameter.
+ * @param ID Name of the parameter.
+ * @param DESC Description of the parameter (1-2 sentences).
+ * @param ALIAS Alias for this parameter (one letter).
+ * @param DEF Default value of the parameter.
+ * @param REQ Whether or not parameter is required (boolean value).
+ */
+#ifdef __COUNTER__
+  #define PARAM_IN(T, ID, DESC, ALIAS, DEF, REQ) \
+      static mlpack::util::Option<T> \
+      JOIN(cli_option_dummy_object_in_, __COUNTER__) \
+      (false, DEF, ID, DESC, ALIAS, REQ, true);
+
+  #define PARAM_OUT(T, ID, DESC, ALIAS, DEF, REQ) \
+      static mlpack::util::Option<T> \
+      JOIN(cli_option_dummy_object_out_, __COUNTER__) \
+      (false, DEF, ID, DESC, ALIAS, REQ, false);
+
+  /** @cond Don't document internal macros. */
+  #define PARAM_FLAG_INTERNAL(ID, DESC, ALIAS) static \
+      mlpack::util::Option<bool> JOIN(__io_option_flag_object_, __COUNTER__) \
+      (ID, DESC, ALIAS);
+  /** @endcond */
+
+#else
+  // We have to do some really bizarre stuff since __COUNTER__ isn't defined.  I
+  // don't think we can absolutely guarantee success, but it should be "good
+  // enough".  We use the __LINE__ macro and the type of the parameter to try
+  // and get a good guess at something unique.
+  #define PARAM_IN(T, ID, DESC, ALIAS, DEF, REQ) \
+      static mlpack::util::Option<T> \
+      JOIN(JOIN(io_option_dummy_object_in_, __LINE__), opt) \
+      (false, DEF, ID, DESC, ALIAS, REQ, true);
+
+  #define PARAM_OUT(T, ID, DESC, ALIAS, DEF, REQ) \
+      static mlpack::util::Option<T> \
+      JOIN(JOIN(io_option_dummy_object_out_, __LINE__), opt) \
+      (false, DEF, ID, DESC, ALIAS, REQ, false);
+
+  /** @cond Don't document internal macros. */
+  #define PARAM_FLAG_INTERNAL(ID, DESC, ALIAS) static \
+      mlpack::util::Option<bool> JOIN(__io_option_flag_object_, __LINE__) \
+      (ID, DESC, ALIAS);
+  /** @endcond */
+
+#endif
+
+#endif
diff --git a/src/mlpack/tests/cli_test.cpp b/src/mlpack/tests/cli_test.cpp
index d0ebbd3..7e8157e 100644
--- a/src/mlpack/tests/cli_test.cpp
+++ b/src/mlpack/tests/cli_test.cpp
@@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(TestOption)
 {
   // This test will involve creating an option, and making sure CLI reflects
   // this.
-  PARAM(int, "test_parent/test", "test desc", "", DEFAULT_INT, false);
+  PARAM_IN(int, "test_parent/test", "test desc", "", DEFAULT_INT, false);
 
   BOOST_REQUIRE_EQUAL(CLI::GetDescription("test_parent/test"), "test desc");
   BOOST_REQUIRE_EQUAL(CLI::GetParam<int>("test_parent/test"), DEFAULT_INT);




More information about the mlpack-git mailing list