Hacker News new | past | comments | ask | show | jobs | submit login
Sub-processing with modern C++ (templated-thoughts.blogspot.com)
51 points by ingve on March 20, 2016 | hide | past | favorite | 14 comments



I handle that with two variadic functions (arguments are stringified and spaced):

    template <typename... A> int shell(A... arguments)
    template <typename... A> string execute(A... arguments)
shell() does a system() and returns the exit code (so the output is echoed in the terminal).

execute() launch a process with popen() and returns the captured steam as a string.

About capture of stderr with fork(), I like the way it's done in Node.js: https://nodejs.org/api/child_process.html

shell: https://github.com/vmorgulys/sandbox/blob/master/stackcity/s...

pfile: https://github.com/vmorgulys/sandbox/blob/master/stackcity/s...


    auto ret = sp::call({"ls", "-l"}, shell{true});
How does this "shell{true}" bit work? It looks like a named parameter, but I thought those don't exist in C++.


It's making an object of type "shell" with true as a constructor argument. But I believe it works like named parameters in that you could use any combination of parameters in any order. This works by making sp::call a variadic template, with the parameter "parsing" happening at compile time based on the types of these wrapper objects.


Exactly. I imagine it's something like the following:

  #include <iostream>
  #include <string>
  #include <utility>
  namespace sp {
    template <typename T>
    struct option {
      template <typename U>
      option(U&& u) : mValue(std::forward<U>(u)) {}
      const T& getValue() const { return mValue; }
      private:
      T mValue;
    };
    struct shell : option<bool> { using option<bool>::option; };
    struct output : option<std::string> { using option<std::string>::option; };
    struct options {
      bool mShell;
      std::string mOutput;
    };
    void applyOpt(options& opts, const shell& opt)
    {
      opts.mShell = opt.getValue();
      std::cout << "shell=" << (opts.mShell ? "true" : "false") << '\n';
    }
    void applyOpt(options& opts, const output& opt)
    {
      opts.mOutput = opt.getValue();
      std::cout << "output=" << opts.mOutput << '\n';
    }
    void applyOpts(options&) {}
    template <typename HeadOpt, typename... TailOpts> void applyOpts(options& opts, HeadOpt&& headOpt, TailOpts&&... tailOpts)
    {
      applyOpt(opts, std::forward<HeadOpt>(headOpt));
      applyOpts(opts, std::forward<TailOpts>(tailOpts)...);
    }
    template <typename... Opts> int call(std::initializer_list<const char*> args, Opts&&... opts)
    {
      std::cout << "args=";
      const char* sep = "";
      for (auto&& s : args)
      {
        std::cout << sep << s;
        sep = " ";
      }
      std::cout << '\n';
      options options;
      applyOpts(options, std::forward<Opts>(opts)...);
      return 0;
    }
  }
  int main()
  {
    sp::call({"ls", "-l"}, sp::shell{true}, sp::output{"foo.txt"});
    return 0;
  }


Oh neat. Also thanks for reducing the use to a 'small' example.

Why does the author use a struct and "shell{true}" rather than creating a function or a class that would allow "shell(true)". Love for braces? Performance?

EDIT: also I wonder whether there's a way to create helper definitions to allow defining named parameters like this in a more concise way. This recursive template expansion business tends to result in long code.


Ok, so it seems the use of braces (use of "Uniform initialization syntax") in constructors is to reduce the ambiguity with declaring functions and declaring+initializing variables. This wiki entry has more:

https://en.wikipedia.org/wiki/Most_vexing_parse


It's a very good day. I get to just say "exactly" again. :)


Thanks for taking the time to explain that :)


It is incorrect to use:

  rc = subprocess.call(["ls", "-l"], shell=True)
It means a different thing. OP should use either:

  rc = subprocess.call(["ls", "-l"])
Or:

  rc = subprocess.call("ls -l", shell=True)
Note: there is no need to use the shell or even to use the external processs in this case.

The example shows the usability issue in the API. Newly designed C++ API shouldn't copy such errors (if it has the same behavior). A list argument with shell=True should be rejected.


What does that curly brace pair mean in isolation?

auto ret = sp::call({"ls", "-l"});

It's not a lambda or a typical block. I don't think I've seen that before.


Specifically in this case it calls

    template<typename... Args> int call(std::initializer_list<const char*> plist, Args&&... args)
Where "ls", and "-l" form the contents of the initializer list.


C++11 allows curly brace initialization: http://www.informit.com/articles/article.aspx?p=1852519



It's about time.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: