2

Is there a way to conditionally add arguments to a constructor? I'd also like to know what this type of construction is called so I can search it myself.

I'm creating a boost::process::child using a constructor where I can pass any properties and things are mostly working great:

    m_proc = new boost::process::child(
        m_context,
        boost::process::exe       = m_config.exe,
        boost::process::args      = m_config.args,
        boost::process::env       = m_config.Environment,
        boost::process::start_dir = m_config.WorkingDirectory,
        boost::process::std_out   > m_stdout_pipe,
        boost::process::std_err   > m_stderr_pipe,
        boost::process::on_exit   = [this](int i, auto e){OnProcExit(i, e);},
        boost::process::extend::on_setup   = [this](auto&){OnProcSetup();},
        boost::process::extend::on_success = [this](auto&){OnProcSuccess();},
        boost::process::extend::on_error   = [this](auto&, auto ec){OnProcError(ec);}
    );

UNTIL I call ls with no arguments. Then it returns

/usr/bin/ls: cannot access '': No such file or directory
Process Exited (code:2)

If m_config.args is empty, I want to avoid passing it. I tried:

    m_proc = new boost::process::child(
        ...
        boost::process::exe       = m_config.exe,
        m_config.args.empty() ? (void) : (boost::process::args = m_config.args),
        ...
    );

but that gives:

error: expected primary-expression before ‘void’

I tried:

    m_proc = new boost::process::child(
        ...
        boost::process::exe       = m_config.exe,
        boost::process::args      = m_config.args.empty() ? {} : m_config.args,
        ...

But that gives:

initializer list cannot be used on the right hand side of operator ?
error: expected primary-expression before ‘{’ token

I understand that for this particular case, I could combine exe and args to make a cmd, but I'd also like to conditionally add other arguments like boost::process::shell or boost::process::stdin.

If I need to call different constructor code for every set of options, I would need to write N! calls to constructors where N is the number of options and that grows fast.

Stewart
  • 4,356
  • 2
  • 27
  • 59

1 Answers1

0

This is ugly.

C++ isn't python, there isn't any named parameters in C++, so this solution makes use of global variables (boost::process::args) which are fundamentally not thread safe and prone to usage errors as you experienced.

In the boost documentation they state you don't need to use the global vars, so you can directly use the your config members here:

m_proc = new boost::process::child(
        m_context,
        m_config.exe,
        m_config.args,
        m_config.Environment,
[...]

By the way, the error you're reporting isn't due to a bad empty list passing (you can have m_config.args = {}) but probably to a wrong command argument list creation (if the list is empty, the boost::process::child code should create an non-empty string for the process's argument list, containing the executable name as the first argument).

The error you are reporting:

/usr/bin/ls: cannot access '': No such file or directory

is likely due to the wrong argument list being generated by boost and it's not due to your (empty) args array.

So I would put a debug breakpoint on the Popen syscall here and walk backward until I figure out what went wrong in building the argument string.

Using a global variable like a parameter here is syntax candy, but it means operator overloading to an unspecified object (as stated in the documentation), so you actually don't know what's going on here (a = b when a is unknown can be anything in C++, like a crazy making b set to the value of a as in T& operator =(T & b) { b = *this; return *this; }). You'll need to debug what's going on here to figure out what is happening. As a short advice, try to avoid using undefined object if it's not required and unclear, and stick to usual C++ practices.

xryl669
  • 3,376
  • 24
  • 47