• Stars
    star
    9
  • Rank 1,882,210 (Top 39 %)
  • Language
    C++
  • License
    MIT License
  • Created over 4 years ago
  • Updated over 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Named Optional Arguments in C++17

Optional Argument in C++

News

Mon 02 Dec 2019 [0.0.2 tag]

Cancel any possible implicit conversion by making constructors explicit.

Thu 21 Nov 2019 [0.0.1 tag]

Added Named_Assert_Type type. See named_assert_example.cpp section.

Fri 01 Nov 2019

Added CMake constructor

What is it?

This is a C++17 one header file library that will allow you to define named optional arguments.

It can be used to improves code readability by replacing obscure function/method calls like

algorithm(x_init, 100, std::vector<double>(n, 0), std::vector<double>(n, 1), 1e-6, 1e-6)

By something like

algorithm(x_init, max_iterations = 100, absolute_precision = 1e-6);
algorithm(x_init, absolute_precision = 1e-6, lower_bounds<double> = std::vector<double>(n, 0));
algorithm(x_init);

supporting all the variations in position/definition for the optional arguments.

The required boilerplate code to add optional argument support is relatively light:

template <typename T, typename... USER_OPTIONS>
void algorithm(std::vector<T>& x, const USER_OPTIONS&... user_options)
{
  Absolute_Precision absolute_precision{1e-10};
  Max_Iterations max_iterations{500};
  std::optional<Allows_Restart> allows_restart;

  auto options = take_optional_argument_ref(absolute_precision, max_iterations, allows_restart);
  optional_argument(options, user_options...);

  // ...
}

Basically it works by defining a std::tuple<OPTIONS...> which is filled by the provided user_options at the site call.

Installation

The library currently uses the meson build system. Maybe I will add CMake in the future, but as this library is only one header file you can easily test it without any build system.

If you are not familiar with meson, the compilation procedure is as follows:

git clone [email protected]:vincent-picaud/OptionalArgument.git
cd OptionalArgument/
meson build
cd build
ninja test

Examples can then be found in the examples/ directory.

CMake

Updated: Fri 01 Nov 2019 08:53:46 AM CET

For convenience, I just have included a CMake build solution that should work.

Tutorial

Basic usage

The most β€œrustic” usage is:

#include <iostream>
#include "OptionalArgument/optional_argument.hpp"

using namespace OptionalArgument;

template <typename... Ts>
void example(Ts... user_options)
{
  int maximum_iterations{50};
  double absolute_precision{1e-5};
  std::optional<bool> allows_restart;

  auto options = take_optional_argument_ref(maximum_iterations, absolute_precision, allows_restart);
  optional_argument(options, user_options...);

  std::cout << "\nOption values: " << options;
}

int main()
{
  example(10);
  example(1e-6, std::optional<bool>(true));
  example();

  return EXIT_SUCCESS;
}
Option values: 10 1e-05 
Option values: 50 1e-06 1 
Option values: 50 1e-05

Basically we define some optional arguments like absolute_precision. Using a helper function take_optional_argument_ref() we collect their references and store them in the options object. Then optional argument values are overwritten by user_options provided at the call site. This task is performed by the optional_argument() function.

Named_Type

Preamble: great blog posts about Named_Typed can be found here. This was a source of inspiration but our implementation is different and simpler as it only provides functionalities useful for our OptionalArgument library.

There are several limitations if we only stick to types like int, double, std::string ... :

  • we cannot define several options of the same type,
  • we cannot relies on implicit type conversion.

By example, if you try:

#include "OptionalArgument/optional_argument.hpp"
#include <iostream>

using namespace OptionalArgument;

template <typename... Ts>
void example(Ts... user_options)
{
  size_t maximum_iterations{50};   // <- size_t instead of int
  float absolute_precision{1e-5};  // <- float instead of double

  auto options = take_optional_argument_ref(maximum_iterations, absolute_precision);
  optional_argument(options, user_options...);

  std::cout << "\nOption values: " << options;
}

int main()
{
  example(10);    // does not work, one would have to use: example(size_t(10));
  example(1e-6);  // does not work, one would have to use: example(1e-6f);

  return EXIT_SUCCESS;
}

the library does not compile and returns an error message:

...
OptionalArgument/optional_argument.hpp:122:46: error: static assertion failed: Unexpected type
static_assert((occurence_count == 1) || (occurence_count_maybe_optional == 1), "Unexpected type");
...

The solution is to use implicit type conversion and a different type for each option.

This library provides a basic Named_Type class for that. Please, note that you can use this library with any of your own class, it is by no way mandatory to use the provided Named_Type.

Using Named_Type the problematic initial code is fixed/rewritten as follows:

#include "OptionalArgument/optional_argument.hpp"

#include <iostream>

using namespace OptionalArgument;

using Absolute_Precision = Named_Type<struct Absolute_Precision_Tag, double>;
constexpr auto absolute_precision = typename Absolute_Precision::argument_syntactic_sugar();

using Maximum_Iterations = Named_Type<struct Maximum_Iterations_Tag, size_t>;
constexpr auto maximum_iterations = typename Maximum_Iterations::argument_syntactic_sugar();

template <typename... Ts>
void example(Ts... user_options)
{
  Maximum_Iterations maximum_iterations{50};
  Absolute_Precision absolute_precision{1e-5};

  auto options = take_optional_argument_ref(maximum_iterations, absolute_precision);
  optional_argument(options, user_options...);

  std::cout << "\nOption values: " << options;
}

int main()
{
  example(maximum_iterations = 10);
  example(absolute_precision = 1e-6);

  return EXIT_SUCCESS;
}
Option values: 10 1e-05 
Option values: 50 1e-06

Without Named_Type

You can use this library without any reference to the Named_Type class (which is only provided for convenience).

The example below shows how you can define optional arguments from scratch and use them.

#include "OptionalArgument/optional_argument.hpp"

#include <iostream>
#include <random>

using namespace OptionalArgument;

struct Sample_Size
{
  size_t n;

  size_t
  value() const
  {
    return n;
  }
};

struct Truncated
{
};

static constexpr auto sample_size = Argument_Syntactic_Sugar<Sample_Size, size_t>();
static constexpr auto truncated   = Truncated();

template <typename... USER_OPTIONS>
void
generate_sample(USER_OPTIONS&&... user_options)
{
  //// Options ////
  //
  Sample_Size sample_size{10};
  std::optional<Truncated> truncated;

  auto options = take_optional_argument_ref(sample_size, truncated);
  optional_argument(options, std::forward<USER_OPTIONS>(user_options)...);

  //// Implementation ////
  //
  std::random_device rd{};
  std::mt19937 gen{rd()};

  std::normal_distribution<> d{0, 1};

  for (size_t i = 0; i < sample_size.value(); i++)
  {
    auto sample = d(gen);
    if (truncated.has_value())
    {
      sample = std::abs(sample);
    }
    std::cout << sample << std::endl;
  }
  std::cout << std::endl;
}

int
main()
{
  generate_sample();

  generate_sample(sample_size = 5);

  generate_sample(truncated, sample_size = 5);
}
1.45544
-0.642569
0.377425
0.276248
1.48404
0.938607
0.575446
-1.49081
1.50139
0.142015

-0.664602
-0.184922
-0.415816
1.09387
-0.0196457

0.348479
1.76989
0.558797
0.835355
1.31337

More examples

You will find associated code in the examples/ directory.

algorithm_usage_example.cpp

An hypothetical optimization algorithm with several options, with some using the std::vector class.

int
main()
{
  const size_t n = 4;
  std::vector<double> x_init(n);

  // Option values: 100 1e-10 1e-10
  optimization_algorithm(x_init);

  // Option values: 50 1e-10 1e-10 0 0 0 0
  optimization_algorithm(x_init, max_iterations = 50,
                         lower_bounds<double> = std::vector<double>(n, 0));

  // Option values: 50 1e-08 1e-10 0 0 0 0  1 1 1 1
  optimization_algorithm(x_init, max_iterations = 50, absolute_precision = 1e-8,
                         lower_bounds<double> = std::vector<double>(n, 0),
                         upper_bounds<double> = std::vector<double>(n, 1));
}

plot_usage_example.cpp

Another use case that shows how one can easily generate gnuplot script commands with all their variations.

int
main()
{
  // prints:
  // plot sin(x)  linetype 2 title "my curve 1"
  // replot cos(x) linewidth 4 title "my curve 2"
  //
  plot(std::cout, "sin(x)", line_type = 2, curve_title = "my curve 1");
  replot(std::cout, "cos(x)", line_width = 4, curve_title = "my curve 2");
}

named_assert_example.cpp

The Named_Assert_Type type allows to control parameter value. A usage example is as follows:

#include "OptionalArgument/optional_argument.hpp"

#include <cassert>

using namespace OptionalArgument;

template <typename T>
struct Assert_Positive
{
  void
  operator()(const T& t) const
  {
    assert(t > 0);
  }
};

using Absolute_Precision =
    Named_Assert_Type<struct Absolute_Precision_Tag, Assert_Positive<double>, double>;
constexpr auto absolute_precision = typename Absolute_Precision::argument_syntactic_sugar();

void
my_algorithm(const Absolute_Precision& absolute_precision)
{
}

int
main()
{
  my_algorithm(absolute_precision = +1e-6);
  my_algorithm(absolute_precision = -1e-6);  // run-time assert fails
}

named_std_fonction.cpp

This allows to wrap std function.

Note: we cannot directly wrap Ξ» as we need some type erasure to use optional argument.

#include "OptionalArgument/optional_argument.hpp"

#include <cassert>
#include <valarray>

using namespace OptionalArgument;

using Objective_Function =
    Named_Std_Function<struct Objective_Function_Tag, double, const std::valarray<double>&>;
constexpr auto objective_function = Argument_Syntactic_Sugar<Objective_Function>();

void
my_algorithm(const Objective_Function& obj_f, std::valarray<double>& x_init)
{
  std::cout << "Value = " << obj_f(x_init) << std::endl;
}

double
Rosenbrock(const std::valarray<double>& x, double c)
{
  assert(x.size() == 2);

  return (1 - x[0]) * (1 - x[0]) + c * (x[1] - x[0] * x[0]) * (x[1] - x[0] * x[0]);
}

double
Rosenbrock(const std::valarray<double>& x)
{
  return Rosenbrock(x, 10);
}

template <typename T>
struct Rosenbrock_as_Struct
{
  double c = 200;

  T
  operator()(const std::valarray<T>& x) const
  {
    assert(x.size() == 2);

    return (1 - x[0]) * (1 - x[0]) + c * (x[1] - x[0] * x[0]) * (x[1] - x[0] * x[0]);
  }
};

int
main()
{
  std::valarray<double> x(2);
  x = -1;

  my_algorithm(objective_function = Rosenbrock, x);

  my_algorithm(
      objective_function = [](const std::valarray<double>& x) { return Rosenbrock(x, 100); }, x);

  my_algorithm(objective_function = Rosenbrock_as_Struct<double>(), x);

  Rosenbrock_as_Struct<double> f;
  my_algorithm(objective_function = f, x);
}

FAQ

-> your questions here :-)

More Repositories

1

Bazel_and_CompileCommands

Add compile_commands.json to your C++ Bazel Project
Shell
123
star
2

GnuPlotScripting

A simple C++17 lib that helps you to quickly plot your data with GnuPlot
C++
45
star
3

Bazel_with_GTest

C++ project skeleton with Bazel & GTest
Starlark
21
star
4

MissionImpossible

A concise C++17 implementation of automatic differentiation (operator overloading)
C++
19
star
5

DirectConvolution.jl

Direct Convolution (no FFT) in Julia
Julia
11
star
6

Blog_CMake_GoogleBenchmark

Blog post "CMake + Google micro-benchmarking"
CMake
9
star
7

Project-Template-For-OrgMode

A project template generator I use for all projects I have to work on
Shell
8
star
8

Julia_with_OrgMode_Example

An example showing how to use Julia & OrgMode, emphasis on PDF/HTML export
Emacs Lisp
8
star
9

Joint_Baseline_PeakDeconv

Linear MALDI-ToF simultaneous spectrum deconvolution and baseline removal
C++
8
star
10

meson_starter_script

A simple script to create a messon project skeleton
Shell
6
star
11

LinearAlgebra

Linear algebra (work in progress)
C++
4
star
12

J4Org.jl

A package to generate Julia code documentation in Emacs OrgMode documents
Julia
4
star
13

Some_Pluto_notebooks

https://vincent-picaud.github.io/Some_Pluto_notebooks
Julia
3
star
14

CMakeScript

A linux shell script to generate C++ project skeletons with a CMake based build system
Shell
3
star
15

mma_meson_demo

How to use Meson to build a Mathematica package relying on LibraryLink and on an external C++ library
Meson
2
star
16

NLS_Solver.jl

A bound constrained nonlinear least squares solver
Julia
2
star
17

SafeOptions

A Mathematica package to use & manipulate options in a safer way
Mathematica
1
star
18

DropBoxRepository

A place to put stuff like code snippets, draft codes...
HTML
1
star
19

GnuplotScripting.jl

An easy to use and simple gnuplot wrapping in Julia
Julia
1
star
20

NLS_Fit.jl

Define some tools for nonlinear squares fitting (using my NLS_Solver.jl package)
Julia
1
star