Reputation: 153
The Ghostscript interpreter API has a function
GSDLLEXPORT int GSDLLAPI gsapi_init_with_args(void *instance, int argc, char **argv)
The final argument argv
is a pointer to an array of C strings, which are interpreted as command-line arguments. I obviously cannot change the signature of the function gsapi_init_with_args
to take a const char **
argument instead.
If I were willing to ignore (or silence) the deprecated conversion from string constant to 'char*'
warning, then I would write simply
char *gs_argv[] = {"", "-dNOPAUSE", "-dBATCH", ...};
and pass gs_argv
as the final argument. But I would prefer to fix my code so that I am not relying on an external function to behave in the way I expect it to (and effectively treat gs_argv
as const char**
).
Is there any simple way to declare gs_argv
as an array of pointers to (non-const
) C strings, and initialize its elements with string literals? (That is, using a similar approach to how I can initialize a single C string: using char c_str[] = "abc"
.) The best I can think of is to use
const char *gs_argv0[] = {"", "-dNOPAUSE", "-dBATCH", ...};
and then copy the contents, element by element, into gs_argv
.
Please note that I understand why the compiler gives this warning (and have read the answers to, among others, this question). I am asking for a solution, rather than an explanation.
Upvotes: 1
Views: 284
Reputation: 98495
Since this code requires C++11, there's a lower cost C++11 solution in another answer below. I'm leaving this one for posterity.
There are pretty much two choices: ignore it and const_cast
, or do the right thing. Since this is modern C++, you're supposed to have nice, RAII classes. Thus, the simplest, safest thing to do is to safely wrap such an array.
// https://github.com/KubaO/stackoverflown/tree/master/questions/args-cstrings-32484688
#include <initializer_list>
#include <type_traits>
#include <cstdlib>
#include <cassert>
#include <vector>
class Args {
struct str_vector : std::vector<char*> {
~str_vector() { for (auto str : *this) free(str); }
} m_data;
void append_copy(const char * s) {
assert(s);
auto copy = strdup(s);
if (copy) m_data.push_back(copy); else throw std::bad_alloc();
}
public:
Args(std::initializer_list<const char*> l) {
for (auto str : l) append_copy(str);
m_data.push_back(nullptr);
}
template <std::size_t N>
Args(const char * const (&l)[N]) {
for (auto str : l) append_copy(str);
m_data.push_back(nullptr);
}
/// Initializes the arguments with a null-terminated array of strings.
template<class C, typename = typename std::enable_if<std::is_same<C, char const**>::value>::type>
Args(C l) {
while (*l) append_copy(*l++);
m_data.push_back(nullptr);
}
/// Initializes the arguments with an array of strings with given number of elements.
Args(const char ** l, size_t count) {
while (count--) append_copy(*l++);
m_data.push_back(nullptr);
}
Args(Args && o) = default;
Args(const Args &) = delete;
size_t size() const { return m_data.size() - 1; }
char ** data() { return m_data.data(); }
bool operator==(const Args & o) const {
if (size() != o.size()) return false;
for (size_t i = 0; i < size(); ++i)
if (strcmp(m_data[i], o.m_data[i]) != 0) return false;
return true;
}
};
Let's see how it works:
#include <iostream>
extern "C" int gsapi_init_with_args(void*, int argc, char** argv) {
for (int i = 0; i < argc; ++i)
std::cout << "arg " << i << "=" << argv[i] << std::endl;
return 0;
}
int main()
{
Args args1 { "foo", "bar", "baz" };
const char * args2i[] { "foo", "bar", "baz", nullptr };
Args args2 { (const char **)args2i };
const char * args3i[] { "foo", "bar", "baz" };
Args args3 { args3i };
const char * const args4i[] { "foo", "bar", "baz" };
Args args4 { args4i };
const char * args5i[] { "foo", "bar", "baz" };
Args args5 { args5i, sizeof(args5i)/sizeof(args5i[0]) };
assert(args1 == args2);
assert(args2 == args3);
assert(args3 == args4);
assert(args4 == args5);
gsapi_init_with_args(nullptr, args1.size(), args1.data());
}
Output:
arg 0=foo
arg 1=bar
arg 2=baz
Upvotes: 1
Reputation: 98495
Inspired by n.m.'s C++14 version, here's a C++11 version. The trick is to use an evaluated empty lambda expression to generate a fresh type, so that each instantiation of W__
is unique.
template <typename T, int N> static char * W__(const char (&src)[N], T) {
static char storage[N];
strcpy(storage, src);
return storage;
}
#define W(x) W__(x, []{})
char * argv[] = {
W("foo"),
W("bar")
};
The static
in front of W__
's return type means that W__
has internal linkage and won't bloat the object file with extra symbols. It has nothing to do with the static
in front of storage
, as the latter indicates the static storage duration for the local variable. The code below would be perfectly valid, but of course doing the wrong thing and having undefined behavior:
template <typename T, int N> static char * BAD(const char (&src)[N], T) {
char storage[N];
strcpy(storage, src);
return storage;
}
Since a lambda has to be evaluated, you can't simply make its type a template argument:
template<typename> void G();
G<decltype([]{})>(); // doesn't work
Upvotes: 0
Reputation: 120031
A C++14 solution.
#define W(x) \
(([](auto& s)->char* \
{ \
static char r[sizeof(s)]; \
strcpy (r, s); \
return r; \
})(x))
char* argv[] =
{ W("--foo=bar",
W("baz"),
nullptr
};
Upvotes: 1
Reputation: 21803
If you can guarantee that the function will not modify the non-const parameter, then it is acceptable to use const_cast
in this situation.
Upvotes: 1
Reputation: 26599
Create copies of the string literals using strdup
. This is more verbose, but fixes the warning.
char* gs_argv0[NARGS];
gs_argv0[0] = strdup("");
gs_argv0[1] = strdup("-dNOPAUSE");
// ...
Note that you will also need to free the memory allocated by strdup
if you want to prevent leaks.
You might also want to add a comment to your code saying why you are doing this, to make it clear for future readers.
Upvotes: 1
Reputation: 206697
You can use:
char arg1[] = "";
char arg2[] = "-dNOPAUSE";
char arg3[] = "-dBATCH";
char* gs_argv0[] = {arg1, arg2, arg3, NULL};
int argc = sizeof(gs_argv0)/sizeof(gs_argv0[0]) - 1;
gsapi_init_with_args(instance, argc, gs_argv0)
Upvotes: 2
Reputation: 2365
Try to const_cast it:
gsapi_init_with_args(instance, argc, const_cast<char**>(argv));
Maybe it will help with fixing warning.
Upvotes: 0