c++execvp

Best practice to use execvp in C++


At the beginning, I wrote something like this

char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", argv);

However, GCC popped up this warning, "C++ forbids converting a string constant to char*."

Then, I changed my code into

const char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", argv);

As a result, GCC popped up this error, "invalid conversion from const char** to char* const*."

Then, I changed my code into

const char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", (char* const*)argv);

It finally works and is compiled without any warning and error, but I think this is a bit cumbersome, and I cannot find anyone wrote something like this on the Internet.

Is there any better way to use execvp in C++?


Solution

  • You hit a real problem because we are facing two incompatible constraints:

    1. One from the C++ standard requiring you that you must use const char*:

      In C, string literals are of type char[], and can be assigned directly to a (non-const) char*. C++03 allowed it as well (but deprecated it, as literals are const in C++). C++11 no longer allows such assignments without a cast.

    2. The other from the legacy C function prototype that requires an array of (non-const) char*:

      int execv(const char *path, char *const argv[]);
      

    By consequence there must be a const_cast<> somewhere and the only solution I found is to wrap the execvp function.

    Here is a complete running C++ demonstration of this solution. The inconvenience is that you have some glue code to write once, but the advantage is that you get a safer and cleaner C++11 code (the final nullptr is checked).

    #include <cassert>
    #include <unistd.h>
    
    template <std::size_t N>
    int execvp(const char* file, const char* const (&argv)[N])
    {
      assert((N > 0) && (argv[N - 1] == nullptr));
    
      return execvp(file, const_cast<char* const*>(argv));
    }
    
    int main()
    {
      const char* const argv[] = {"-al", nullptr};
      execvp("ls", argv);
    }
    

    You can compile this demo with:

    g++ -std=c++11 demo.cpp 
    

    You can see a similar approach in the CPP Reference example for std::experimental::to_array.