Flow 1.0.0
Flow project: Public API.
Functions
boost::filesystem Namespace Reference

We may add some ADL-based overloads into this namespace outside flow. More...

Functions

void validate (boost::any &target, const std::vector< std::string > &user_values, path *, int)
 ADL-based overload of boost.program_options validate() to allow for empty boost::filesystem::path values in flow::cfg config parsing as well as any other boost.program_options parsing in the application. More...
 

Detailed Description

We may add some ADL-based overloads into this namespace outside flow.

Function Documentation

◆ validate()

void boost::filesystem::validate ( boost::any &  target,
const std::vector< std::string > &  user_values,
path *  ,
int   
)

ADL-based overload of boost.program_options validate() to allow for empty boost::filesystem::path values in flow::cfg config parsing as well as any other boost.program_options parsing in the application.

Likely there's no need to call this directly: it is invoked by boost.program_options when parsing paths.

More precisely: This has ~two effects:

  • flow::cfg::Option_set (and flow::cfg::Config_manager and anything else built on Option_set) will successfully parse an empty (or all-spaces) value for a setting of type boost::filesystem::path, resulting in a path equal to a default-constructed path().
    • You may still disallow it via a validator expression in FLOW_CFG_OPTION_SET_DECLARE_OPTION() as usual. (Sometimes an empty "path" value is useful as a special or sentinel value.) Such flow::cfg-user-specified individual-option-validator checking, internally, occurs after (and only if) the lower-level parsing already succeeded.
  • Any other use (even outside of flow::cfg) of boost.program_options to parse a path will now also allow an empty value (on command line, in file, etc.) in the same application.

Rationale

Allowing empty paths in flow::cfg is required for usability. It was a common use case to allow for a blank special value for path settings. However trying to in fact provide an empty value in a flow::cfg file (e.g., simply log-file=) resulted in an "Invalid argument" error message and refusal of flow::cfg::Option_set::parse_config_file() to successfully parse.

(The following discussion is about implementation details and would normally be omitted from this public-facing API doc header. However, in this slightly unusual (for Flow) situation the solution happens to subtly affect code outside of flow::cfg. Therefore it is appropriate to discuss these internals here.)

The reason for this problem: flow::cfg uses boost.program_options for config source (especially file) parsing. By default, when parsing a string into the proper type T (here T being path), boost.program_options uses istream >> T overload. Turns out that reading a blank from a stream into path causes the istream bad-bit to set, meaning lexical_cast throws bad_lexical_cast; boost.program_options then manifests this as an arg parse error. (If T were std::string, by contrast, no such problem would occur: istream >> string will happily accept a blank string.)

boost.program_options clearly suggests the proper way to custom-parse types at https://www.boost.org/doc/libs/1_78_0/doc/html/program_options/howto.html#id-1.3.32.6.7 – namely, define a validate() overload in the same namespace as type T, with a T-typed arg. ADL (argument-dependent lookup) will then use that overload – instead of the default one, which simply invokes lexical_cast (i.e., istream>>) – and reports an error if that throws. This is the same technique used to conveniently select hash_value() for unordered_*; swap() for STL containers; etc.

This solves the problem. However, somewhat unusually, this imposes the same more-permissive semantics on all other uses of boost.program_options in any application linking Flow (which includes this overload). This is arguably not-great; ideally we'd affect flow::cfg and nothing else. That said (1) there's no apparent alternative in boost.program_options; and more to the point (2) this feels like either an improvement or neutral. If an empty path must be disallowed, this can be done via notifier() (or just a manual check). So, all in all, this seemed fine.

Parameters
targettarget shall be loaded with the path on success. Otherwise we shall throw, per required boost.program_options semantics.
user_valuesThe (trimmed) raw strings from the user for this occurrence of the setting. In our case there must be only one, since a path has only one value.