boost::program_options
appears to support some level of custom validation but it seems odd to me that the validation is written in terms of types, and not per-argument, and I'm wondering if I'm missing something here.
For example, say you have a "file editing" program that takes input
and output
filenames from the command line. Next assume that you want to store these into variables of type boost::filesystem::path
. Now, let's say we have a requirement that the input
file must exist, but the output
file doesn't (i.e. if the output
file doesn't exist, we'll create it.) Ideally, we'd have a way to test that the input
argument exists, and separately that the output
file either exists and is writable or is in a directory that we can write to. (The specific differences here aren't actually relevant. This applies to any situation where you're using the same type in multiple places where you'd like to have different validation rules depending on the use.)
Because we configure validators by creating overrides of validate
(which are presumably found at compile time by their type signatures, it appears we can only have one validator for all instances of boost::filesystem::path
. I've seen the notify
hook, but the signature of those callbacks has a const
qualifier, so it doesn't seem like you could modify the value, and it's not clear in the documentation how throw
ing in a notify
callback would get worked in to the validation system.
This seems like a pretty fundamental limitation to the point where I think I'm probably missing something. Am I? Thoughts?
It never hurts to have one type per concept in a program. If the two paths represent different concepts, give them their own types:
#include <iostream>
#include <cstdlib>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
namespace po = boost::program_options;
namespace fs = boost::filesystem;
template<class Type, class Tag>
struct tagged
{
Type const& get() const { return value_; }
Type& get() { return value_; }
operator Type& () { get(); }
friend decltype(auto) operator>>(std::istream& is, tagged& t) {
return is >> t.get();
}
friend decltype(auto) operator<<(std::ostream& os, tagged const& t) {
return os << t.get();
}
// and so on...
Type value_;
};
using bar_path = tagged<fs::path, class bar_path_tag>;
using foo_path = tagged<fs::path, class foo_path_tag>;
int main()
{
bar_path bar;
foo_path foo;
po::options_description desc("prog");
desc.add_options()
("foo", po::value(&foo), "a foo path")
("bar", po::value(&bar), "a bar path")
;
}