🔗 The Problem

You wrote a beautiful lil’ Python script. :snake:

You run it on its own sometimes, but you’d also like to dispatch it from some C++ code.

We could just std::system() the script from wherever it resides, but then we’d have to keep track of where it resides and make sure it’s available everywhere we want to execute our executable

We could always #include the text of the script file into our source. But we would want to shove it inside of a string.

In a perfect world, a raw string literal, which lets you shove arbitrary text into a string into without worrying about escaping, would be the ideal vehicle for these shenanigans. Unfortunately, for a whole host of very depressing reasons, we can’t #include into a raw string literal. (Among other reasons, the #include directive would be interpreted as, well, raw text.)

Wanting to embed data into C++ source is a common-enough concern that there are plenty of well-tread workarounds. We could use xxd to generate a header with the script inside a unsigned char[], but keeping that autogenerated header in sync with the script source would complicate our build process. There’s some finagling in the standards committee that might eventually make this problem less annoying, but at best that’s a ways off.

🔗 The Solution

Turns out that Python also has raw string literal syntax. Conveniently, these strings also start out with R". :thinking:

Also, Python is perfectly happy to ignore any loose strings you may have floating around. For example,

print( 'foobar' )
'this string gets ignored'
print( "boop boop it's python time" )

Indeed, Python will happily ignore any loose strings, say, you might happen to position the beginning and end of a script. :monocle_face:

See where this is going?

🔗 The Abomination

Where it is going is that we’re going to shove the C++ raw string literal syntax into our python script and then #include that.

example.py:

R"delimiter(" # allows us to #include this script into C++ source

import sys

# doing all my normal python stuff

def greet(who):
  print( "hello", who )

if __name__ == '__main__':

  __, who = sys.argv
  greet(who)

# allows us to #include this script into C++ source
")delimiter"

main.cpp:

#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <string>
#include <system>

// contains stript as a string, with an extra " character at beginning/end
const std::string raw_script_source{
  #include "example.py"
};

// temporary file we'll dump script into
const std::string script_path{
  std::tmpnam(nullptr)
};

int main() {

  // dump script into temporary file
  {
    std::ofstream out( script_path );
    // drop leading and trailing "'s from raw_script_source
    out << raw_script_source.substr(1, raw_script_source.size() - 2);
  }

  // construct command we'll pass to std::system
  std::stringstream ss;
  ss << "python3 " << script_path << " m_lady";

  const std::string command{ ss.str() };

  // dispatch python script
  std::system( command.c_str() );

}

:smiling_imp: Proceed directly to hell. Do not pas Go, do not collect $200.

Our Python script will execute as we expect it to when we run it on its own. With this setup, we can’t have a shebang at the top, though.

The only really tricky bit is that this method results with an extra “ at the beginning and end of the script’s raw string literal in C++. We have to strip those out manually.

🔗 Give it a Whirl

I am absolutely certain there would be no shortage of bash shell in hell… so here’s how you’d compile and run this code on it. :upside_down_face:

g++ main.cpp
./a.out

🔗 Let’s Chat

Comments? Questions? Does this post inspire you to write all your comments as loose strings??? :wink:

I’d love to hear about any related hacks you’re up to!

I started a twitter thread (right below) so we can chat :phone: :phone: :phone: