Question

I have stumbled upon problem defined in the title. I have an application that creates an instance of options_description then uses add_options() on it. Pretty much like in the example:

options_description desc;
desc.add_options()
("help", "produce help")
("optimization", value<int>()->default_value(10), "optimization level")
;

My question is, how could one modify the default value for the optimization after this call. Is this even possible? The documentation seems pretty murky to me. From what I understand, the question can be generalized to any value semantic, as value_semantic is the 2nd parameter in the parentheses.

Motivation

I get the feeling this might be not possible. So I would like to present my motivation for such functionality. Maybe my intented design is flawed, so you can suggest something else.

I have several programs that perform quite similar tasks and share quite some parameters and switches. I thought I can refactor the common parameters to a separate base class. I though that I can refactor the command line parsing along in similar manner. boost::program_options do work quite well with my idea. I construct options_description instance as a private property in the base class and add common options there. Then in derived classes upon initialization I execute add_options() on this object again adding more specific options. This seemed quite neat and I got it working pretty fast.

Then I noticed that all of the derived classes would have a common option, but it would be really nice for it to have a different default value. I.e. name of the output file. For app1 to be app1.out, app2 - app2.out etc.

Of course I can move the output filename option to add_options in the derived classes but it seems stupid and redundand since even semantically, everything is the same except the default value. The other workaround would be to resign from default value in the base class and in the after-parse step in derived classes, check whether the option was set and apply a (the default) value manually. However, this also seems redundant since the intended functionality seems to be implemented in the library itself.

I will try to provide a code example so you can feel it better later on or upon a request. Though, I think my approach is pretty clear.

EDIT - The code examples It was written after Rob's answer so I tried to stay within the naming convention.

Base - performs parsing and allows to set optimization level as integer:

#include <boost/program_options.hpp>
namespace po = boost::program_options;

class BaseClass {
public:
  BaseClass::BaseClass();
  virtual int parse(const int argc, char** argv);
private:
  po::options_description m_desc;
  po::variables_map vm;
  int optimization_level;
};

BaseClass::BaseClass():
  m_desc()
{
  m_desc.add_options()
   ("help", "produce help")
   ("optimization", value<int>()->default_value(10), "optimization level")
  ;
}

int BaseClass::parse(const int argc, char** argv)
{
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);
  if (vm.count("help")) { std::cout << desc << "\n"; return 1; }
  optimization_level = vm["optimization"].as<int>();
  return 0;
}

Highly optimized version which allows to optionally perform fancy stuff:

class HighlyOptimizedClass : public BaseClass {
public:
  HighlyOptimizedClass();
  virtual int parse(const int argc, char** argv);
private:
  bool fancy_optimizations;
};

HighlyOptimizedClass(): BaseClass() {
  m_desc.add_options()
   ("fancy,f", po::value<bool>()->zero_tokens(), "perform fancy optimizations")
  ;
}

HighlyOptimizedClass::parse(const int argc, char** argv)
{
  int ret = BaseClass::parse(argc, argv);      //execute base function
  if( ret ) return ret;                        //return if it didnt succed
  if ( vm.count("fancy") ) fancy_optimizations = 1;  // non-base stuff
  return 0;
}

Non-optimized version which allows to turn on verbose debugging:

class NonOptimizedClass : public BaseClass {
public:
  NonOptimizedClass();
  virtual int parse(const int argc, char** argv);
private:
  bool verbose_debug;
};

NonOptimizedClass(): BaseClass() {
  m_desc.add_options()
   ("verbose,v", po::value<bool>()->zero_tokens(), "genrates TONS of output")
  ;
}

NonOptimizedClass::parse(const int argc, char** argv)
{
  int ret = BaseClass::parse(argc, argv);       // execute base function
  if( ret ) return ret;                         // return if it didnt succed
  if ( vm.count("verbose") ) verbose_debug = 1; // non-base stuff
  return 0;
}

I tried to compress it vertically but it got long anyways =/. Sorry if I went overboard. In turn, the examples are clear and self contained.

The BaseClass sets up pretty much everything and parses common stuff. The derived classes add their own options in constructors and overload parsing. They do execute the base parser and check for errors. This also makes --help work.

Now the thing is to modify the default value of optimizations for each of the derives. As it would be nice to have it set really low for NonOptimizedClass and really high for OptimizedClass.

Était-ce utile?

La solution

You can call options_description::find("optimization", ...) to get a reference to the associated option_description, and its semantic method will give you a pointer to the value_semantic that you originally provided while calling add_options. However, it's a const pointer, so it appears you're not allowed to modify what it points to.

However, the value_semantic wasn't const when you created it, and that means it should be safe to use const_cast to remove the const qualification that option_description applies. You'll also have to type-cast the value_semantic object back to the right typed_value type that you got when you originally called value<T>.

option_description const& optimization = desc.find("optimization", false);
shared_ptr<const value_semantic> cvalue = optimization.semantic();
shared_ptr<value_semantic> value = const_pointer_cast<value_semantic>(cvalue);
shared_ptr<typed_value<int>> tvalue = dynamic_pointer_cast<typed_value<int>>(value);
assert(tvalue);
tvalue->default_value(20);

An alternative design, which avoids having to modify the options after they've been defined (which is clearly not something program_options was designed to do), is to have the program-specific derived class pass the desired default value to the base class. Then the base class can use that value when defining the optimization option.

BaseClass::BaseClass(int default_optimization):
  m_desc()
{
  m_desc.add_options()
    ("help",
      "produce help")
    ("optimization",
      value<int>()->default_value(default_optimization),
      "optimization level")
    ;
}

HighlyOptimizedClass::HighlyOptimizedClass():
  BaseClass(99)
{ }

NonOptimizedClass::NonOptimizedClass():
  BaseClass(0)
{ }

Autres conseils

const std::vector< shared_ptr< option_description > > & options_description::options() const; gives you write access to an option_description.

A const shared_ptr<T> is not a shared_ptr<const T>.

However, I'd instead use add_output_file( std::string default ), and have it call add_option, if I had that kind of problem. (quite possibly, despite the above seeming to be legal from the interface, messing with the internals like that might confuse the boost library.

Ok, so I figured out that in current version it is not possible to alter the value_semantic with out violating the existing design of program_options by const_casting.

After reading Rob Kennedy's and Yakk's answer and suggestions I came up with an approach that is combination between the two. It should work as described and should not clutter anything needlessly.

The idea is to add the option that is intended to be altered in a separate call. Make it virtual and define the default case in the base class.

This approach allows to customize whole program_option at once, not only single semantic. Adding a parameter or method for each single case that could be change seems really cumbersome to me.

The altered codes would look like this:

Base

class BaseClass {
public:
  BaseClass::BaseClass();
  virtual int parse(const int argc, char** argv);
private:
  virtual void add_optimization_option();
  po::options_description m_desc;
  po::variables_map vm;
  int optimization_level;
};

BaseClass::BaseClass(): m_desc() {
  m_desc.add_options()("help", "produce help");
}

void BaseClass::add_optimization_option(){
 m_desc.add_options()
  ("optimization", value<int>()->default_value(10), "optimization level");
}

Optimized version:

class HighlyOptimizedClass : public BaseClass {
public:
  HighlyOptimizedClass();
  virtual int parse(const int argc, char** argv);
private:
  virtual void add_optimization_option();
  bool fancy_optimizations;
};

void HighlyOptimizedClass::add_optimization_option(){
  m_desc.add_options()
   ("optimization", value<int>()->default_value(99), "optimization level");
}

Non-optimized:

class NonOptimizedClass : public BaseClass {
public:
  NonOptimizedClass();
  virtual int parse(const int argc, char** argv);
private:
  virtual void add_optimization_option();
  bool verbose_debug;
};

void NonOptimizedClass::add_optimization_option(){
  m_desc.add_options()
   ("optimization", value<int>()->default_value(0), "optimization level");
}

The cost is to add one private method for each modified option and overload one private method for each case we want to modify it. When one wants to leave it default nothing is needed. If modification of value_semantic was possible defining new methods could be avoided. Nevertheless, except this hindrance it works well and does not clutter anything else.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top