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.
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)
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.
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.
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
};
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
Try to const_cast it:
gsapi_init_with_args(instance, argc, const_cast<char**>(argv));
Maybe it will help with fixing warning.
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
Related
Context:
In my company we generate a lot of types based on IDL files. Some of the types require special logic so they are handcoded but follow the same pattern as the generated ones. We have a function which all types must implement which is a name function. This will return the type name as a char* string and the function is constexpr.
Problem:
The problem is regarding collections which could contain other collections nested potentially N number of times. I therefore am trying to concatenate two or more char* strings at compile time.
Pseudocode of what I want to achieve:
template <typename T>
constexpr char* name()
{
constexpr char* collectionName = "std::vector";
constexpr char* containedTypeName = name<T>();
return concat(collectionName, "<", containedTypeName, ">");
}
Note:
There are examples out there which does something like this but is done with char[] or the use of static variables.
The question:
How can I make a constexpr function which return a char* which consists of two or more concatenated char* strings at compile time? I am bound to C++17.
From constexpr you cannot return char* which is constructed there... You must return some compile time known(also its size) constant thingy.
A possible solution could be something like:
#include <cstring>
// Buffer to hold the result
struct NameBuffer
{
// Hardcoded 128 bytes!!!!! Carefully choose the size!
char data[128];
};
// Copy src to dest, and return the number of copied characters
// You have to implement it since std::strcpy is not constexpr, no big deal.
constexpr int constexpr_strcpy(char* dest, const char* src);
//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr NameBuffer name()
{
// We will return this
NameBuffer buf{};
constexpr const char* collectionName = "std::vector";
constexpr const char* containedTypeName = "dummy";
// Copy them one by one after each other
int n = constexpr_strcpy(buf.data, collectionName);
n += constexpr_strcpy(buf.data + n, "<");
n += constexpr_strcpy(buf.data + n, containedTypeName);
n += constexpr_strcpy(buf.data + n, ">");
// Null terminate the buffer, or you can store the size there or whatever you want
buf.data[n] = '\0';
return buf;
}
Demo
And since the returned char* is only depends on the template parameter in your case, you can create templated variables, and create a char* to them, and it can act like any other char*...
EDIT:
I have just realized that your pseudo code will never work!! Inside name<T>() you are trying to call name<T>().
You must redesign this!!! But! With some hack you can determine the size at compile time somehow for example like this:
#include <cstring>
#include <iostream>
template<std::size_t S>
struct NameBuffer
{
char data[S];
};
// Copy src to dest, and return the number of copied characters
constexpr int constexpr_strcpy(char* dest, const char* src)
{
int n = 0;
while((*(dest++) = *(src++))){ n++; }
return n;
}
// Returns the len of str without the null term
constexpr int constexpr_strlen(const char* str)
{
int n = 0;
while(*str) { str++; n++; }
return n;
}
// This template parameter does nothing now...
// I left it there so you can see how to create the template variable stuff...
//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr auto createName()
{
constexpr const char* collectionName = "std::vector";
constexpr const char* containedTypeName = "dummy";
constexpr std::size_t buff_size = constexpr_strlen(collectionName) +
constexpr_strlen(containedTypeName) +
2; // +1 for <, +1 for >
/// +1 for the nullterm
NameBuffer<buff_size + 1> buf{};
/// I'm lazy to rewrite, but now we already calculated the lengths...
int n = constexpr_strcpy(buf.data, collectionName);
n += constexpr_strcpy(buf.data + n, "<");
n += constexpr_strcpy(buf.data + n, containedTypeName);
n += constexpr_strcpy(buf.data + n, ">");
buf.data[n] = '\0';
return buf;
}
// Create the buffer for T
template<typename T>
static constexpr auto name_buff_ = createName<T>();
// point to the buffer of type T. It can be a function too as you wish
template<typename T>
static constexpr const char* name = name_buff_<T>.data;
int main()
{
// int is redundant now, but this is how you could use this
std::cout << name<int> << '\n';
return 0;
}
Demo
When using an array non-template type parameter, it seems that size information is basically impossible to recover without passing it separately. For example, in a template
template<const char CStr[]>
struct ToTemp {
...
}
any references to sizeof(CStr) will return 8 (or 4, depending on your system), because it's actually a const char *. You can declare
template<const char CStr[5]>
struct ToTemp {
...
}
or
template<int N, const char CStr[N]>
struct ToTemp {
...
}
but the first one requires knowing the actual size when you're writing the class (not very useful), and the second one requires passing in the size separately anyway (not useful here -- could be useful for enforcing size constraints). Ideally I would have something like
template<const char CStr[int N]> //Let the compiler infer N based on the type
struct ToTemp {
...
}
or
template<int N = -1, const char CStr[N]> //Declare N but let it be forced to a given value, so that I don't have to pass it in
struct ToTemp {
...
}
... but of course neither of those work. In the end, I want to be able to write
const char foo[] = "abcd";
ToTemp<foo> bar;
and have bar correctly understand that sizeof(foo) is 5, without having to pass in a separate sizeof(foo) template argument.
You can use global string literals as a template parameter to match const char[] and calculate length via constexpr function:
constexpr size_t GetSize(const char str[])
{
for (size_t i = 0; ; ++i)
{
if (str[i] == '\0')
{
return i;
}
}
}
template <const char str[]>
struct X
{
constexpr static auto Size = GetSize(str);
};
constexpr const char string[] = "42";
void foo()
{
X<string> x;
static_assert(X<string>::Size == 2, "");
}
So I have the following available:
struct data_t {
char field1[10];
char field2[20];
char field3[30];
};
const char *getData(const char *key);
const char *field_keys[] = { "key1", "key2", "key3" };
This code is given to my and I cannot modify it in any way. It comes from some old C project.
I need to fill in the struct using the getData function with the different keys, something like the following:
struct data_t my_data;
strncpy(my_data.field1, getData(field_keys[0]), sizeof(my_data.field1));
strncpy(my_data.field1, getData(field_keys[1]), sizeof(my_data.field2));
strncpy(my_data.field1, getData(field_keys[2]), sizeof(my_data.field3));
Of course, this is a simplification, and more things are going on in each assignment. The point is that I would like to represent the mapping between keys and struct member in a constant structure, and use that to transform the last code in a loop. I am looking for something like the following:
struct data_t {
char field1[10];
char field2[20];
char field3[30];
};
typedef char *(data_t:: *my_struct_member);
const std::vector<std::pair<const char *, my_struct_member>> mapping = {
{ "FIRST_KEY" , &my_struct_t::field1},
{ "SECOND_KEY", &my_struct_t::field2},
{ "THIRD_KEY", &my_struct_t::field3},
};
int main()
{
data_t data;
for (auto const& it : mapping) {
strcpy(data.*(it.second), getData(it.first));
// Ideally, I would like to do
// strlcpy(data.*(it.second), getData(it.first), <the right sizeof here>);
}
}
This, however, has two problems:
It does not compile :) But I believe that should be easy to solve.
I am not sure about how to get the sizeof() argument for using strncpy/strlcpy, instead of strcpy. I am using char * as the type of the members, so I am losing the type information about how long each array is. In the other hand, I am not sure how to use the specific char[T] types of each member, because if each struct member pointer has a different type I don't think I will be able to have them in a std::vector<T>.
As explained in my comment, if you can store enough information to process a field in a mapping, then you can write a function that does the same.
Therefore, write a function to do so, using array references to ensure what you do is safe, e.g.:
template <std::size_t N>
void process_field(char (&dest)[N], const char * src)
{
strlcpy(dest, getData(src), N);
// more work with the field...
};
And then simply, instead of your for loop:
process_field(data.field1, "foo");
process_field(data.field2, "bar");
// ...
Note that the amount of lines is the same as with a mapping (one per field), so this is not worse than a mapping solution in terms of repetition.
Now, the advantages:
Easier to understand.
Faster: no memory needed to keep the mapping, more easily optimizable, etc.
Allows you to write different functions for different fields, easily, if needed.
Further, if both of your strings are known at compile-time, you can even do:
template <std::size_t N, std::size_t M>
void process_field(char (&dest)[N], const char (&src)[M])
{
static_assert(N >= M);
std::memcpy(dest, src, M);
// more work with the field...
};
Which will be always safe, e.g.:
process_field(data.field1, "123456789"); // just fits!
process_field(data.field1, "1234567890"); // error
Which has even more pros:
Way faster than any strcpy variant (if the call is done in run-time).
Guaranteed to be safe at compile-time instead of run-time.
A variadic templates based solution:
struct my_struct_t {
char one_field[30];
char another_field[40];
};
template<typename T1, typename T2>
void do_mapping(T1& a, T2& b) {
std::cout << sizeof(b) << std::endl;
strncpy(b, a, sizeof(b));
}
template<typename T1, typename T2, typename... Args>
void do_mapping(T1& a, T2& b, Args&... args) {
do_mapping(a, b);
do_mapping(args...);
}
int main()
{
my_struct_t ms;
do_mapping(
"FIRST_MAPPING", ms.one_field,
"SECOND_MAPPING", ms.another_field
);
return 0;
}
Since data_t is a POD structure, you can use offsetof() for this.
const std::vector<std::pair<const char *, std::size_t>> mapping = {
{ "FIRST_FIELD" , offsetof(data_t, field1},
{ "SECOND_FIELD", offsetof(data_t, field2)}
};
Then the loop would be:
for (auto const& it : mapping) {
strcpy(static_cast<char*>(&data) + it.second, getData(it.first));
}
I don't think there's any way to get the size of the member similarly. You can subtract the offset of the current member from the next member, but this will include padding bytes. You'd also have to special-case the last member, subtracting the offset from the size of the structure itself, since there's no next member.
The mapping can be a function to write the data into the appropriate member
struct mapping_t
{
const char * name;
std::function<void(my_struct_t *, const char *)> write;
};
const std::vector<mapping_t> mapping = {
{ "FIRST_KEY", [](data_t & data, const char * str) { strlcpy(data.field1, str, sizeof(data.field1); } }
{ "SECOND_KEY", [](data_t & data, const char * str) { strlcpy(data.field2, str, sizeof(data.field2); } },
{ "THIRD_KEY", [](data_t & data, const char * str) { strlcpy(data.field3, str, sizeof(data.field3); } },
};
int main()
{
data_t data;
for (auto const& it : mapping) {
it.write(data, getData(it.name));
}
}
To iterate over struct member you need:
offset / pointer to the beginning of that member
size of that member
struct Map {
const char *key;
std::size_t offset;
std::size_t size;
};
std::vector<Map> map = {
{ field_keys[0], offsetof(data_t, field1), sizeof(data_t::field1), },
{ field_keys[1], offsetof(data_t, field2), sizeof(data_t::field2), },
{ field_keys[2], offsetof(data_t, field3), sizeof(data_t::field3), },
};
once we have that we need strlcpy:
std::size_t mystrlcpy(char *to, const char *from, std::size_t max)
{
char * const to0 = to;
if (max == 0)
return 0;
while (--max != 0 && *from) {
*to++ = *from++;
}
*to = '\0';
return to0 - to - 1;
}
After having that, we can just:
data_t data;
for (auto const& it : map) {
mystrlcpy(reinterpret_cast<char*>(&data) + it.offset, getData(it.key), it.size);
}
That reinterpret_cast looks a bit ugly, but it just shift &data pointer to the needed field.
We can also create a smarter container which takes variable pointer on construction, thus is bind with an existing variable and it needs a little bit of writing:
struct Map2 {
static constexpr std::size_t max = sizeof(field_keys)/sizeof(*field_keys);
Map2(data_t* pnt) : mpnt(pnt) {}
char* getDest(std::size_t num) {
std::array<char*, max> arr = {
mpnt->field1,
mpnt->field2,
mpnt->field3,
};
return arr[num];
}
const char* getKey(std::size_t num) {
return field_keys[num];
}
std::size_t getSize(std::size_t num) {
std::array<std::size_t, max> arr = {
sizeof(mpnt->field1),
sizeof(mpnt->field2),
sizeof(mpnt->field3),
};
return arr[num];
}
private:
data_t* mpnt;
};
But probably makes the iterating more readable:
Map2 m(&data);
for (std::size_t i = 0; i < m.max; ++i) {
mystrlcpy(m.getDest(i), getData(m.getKey(i)), m.getSize(i));
}
Live code available at onlinegdb.
bool foo(const char* arg, const char* str[]){
for (int i=0; i < (signed)sizeof(str); i++){
if(strcmp(arg, str[i])) == 0){
return true;
}
}
return false;
}
int main(){
foo("c", {"a", "b", "c"});
return 0;
}
I want to pass an array of const char* to the function directly like it is shown here. But I keep getting the error: cannot convert '<brace-enclosed initializer list>' to 'const char**'
While there is a way to make that work using templates and passing C-arrays by reference, I'm not sure if that is what you really want: example
In C++11 I'd much prefer to use a std::intializer_list to make the call foo("c", {"a", "b", "c"}) work as intended:
#include <initializer_list>
bool foo(const char* arg, std::initializer_list<const char*> strings) {
for (const char* str : strings) {
/* ... */
}
}
While we're at it, you should consider using C++'s string facilities over those inherited from C:
#include <initializer_list>
#include <string>
bool foo(const std::string& arg, std::initializer_list<std::string> strings) {
for (const auto& str : strings) {
if (arg == str) {
return true;
}
}
return false;
}
Note that a std::initializer_list does not own the values it provides access to, so if you want to store it, use a proper container like std::vector instead.
Also, if checking for the presence of arg in str is all you want to do,
why not use std::find?
#include <algorithm>
#include <initializer_list>
#include <string>
bool foo(const std::string& arg, std::initializer_list<std::string> strings) {
return std::find(strings.begin(), strings.end(), arg) != strings.end();
}
Use a character pointer pointer (char**) to achieve this. You cannot declare parameters as arrays, but you can pass arrays into functions, as they will be converted into double pointers.
You can also pass the array by reference via a template function, so you will be able to detect its size at compile time (note that in your case the array decays to a pointer, so sizeof doesn't do what you think). Example:
#include <iostream>
#include <cstring>
template<int N>
bool foo(const char* arg, const char* (&str)[N])
{
for (std::size_t i = 0; i < N; i++) {
if (std::strcmp(arg, str[i]) == 0) {
return true;
}
}
return false;
}
int main() {
const char* arr[] = {"abc", "de"};
std::cout << std::boolalpha << foo("de", arr);
}
Live on Coliru
And btw, try to use algorithms from the C++ standard library and std::string instead of strcmp() and raw C-strings:
#include <algorithm>
#include <iostream>
#include <vector>
bool foo(const std::string arg, const std::vector<std::string>& str)
{
return std::find(str.begin(), str.end(), arg) != str.end();
}
int main()
{
std::cout << std::boolalpha << foo("de", {"abc", "de"});
}
Live on Coliru
Although it is recommended to drop C-style programming when using C++, and use stl containers instead, I would like to mention that the following "one-liner" works:
bool foo(const char* arg, const char* const* str, size_t siz) {
for (size_t i = 0; i < siz; i++)
if (!strcmp(arg, str[i])) return true;
return false;
}
int main() {
cout << foo("c", array<char*, 3> { "a", "b", "c" }.data(), 3);
system("pause");
}
The surprising surprise is that if we define the second argument of foo as const char**, the compiler (VS2015) yells and says:
bool foo(...): cannot convert argument 2 from 'char **' to 'const char **'
:O
cannot convert from non-const to const??? Does that make sense anyone?
Edit: #NeilKirk explained it in his comment. Normal. Accordingly the signature should be as:
bool foo(const char* arg, const char* const* str, size_t siz)
and everything works as expected.
I need to emulate a library type that allows treating it both as char* & std::string. e.g. this should be allowed:
char c[16];
MyString ms;
...
strcpy(c, ms);
strcpy(c, ms.c_str());
I only control the library but unfortunately can't change this library usage. I cannot change the printf call, just the type sent to it!
I can do this for instance by implementing:
class MyString : public string {
public:
operator const char*();
};
The problem is with this:
sprintf(c, "%s", ms);
The C "arg_list" functions will not use the char* cast.
Any suggestions?
Thanks.
You can pass only trivially-copyable PODs to ....
You could do
struct wrapper { const char * it; };
wrapper it{"hello"};
printf("%s", it);
But the problem is, you cannot manage memory. Once you try to add constructor and destructor to alloc/free the memory, you will be faced with an error.
error: cannot pass objects of non-trivially-copyable type ‘class mystr’ through ‘...’
You can help yourself with:
const char * c_str(const char * it) { return it; }
const char * c_str(const std::string& it) { return it.c_str(); }
const char * cstr = "hello";
std::string cppstr = "world";
printf("%s %s!", c_str(cstr), c_str(cppstr));
Or use boost::format (or similar for ostream). And you should use ostream/format for C++ (type-safe).
You said:
The problem is with this:
sprintf(c, "%s", ms);
The C "arg_list" functions will not use the
char* cast.
There is no cast here. sprintf(..., "%s") is not casting ms to anything, so it will never call your conversion operator. A cast would be: sprintf(c, "%s", (const char *)ms). Unlike strcpy, va_list doesn't cause a coercion as there is no parameter type.
You can pass objects to va_list functions, but it you need to either explicitly cast the arguments to call the correct type conversion, or use .c_str()
If printf() could implicitly handle C++ objects per its format specifiers, it would certainly already be in the language, since it is probably one of the most desired library functions that didn't map over from C.
If you are set on using printf() and family, simply write:
printf("%s", foo.c_str());
Or try something like this (as suggested by #firda in a comment):
const char * c_str(const std:string &s) { return s.c_str(); }
const char * c_str(const char * s) { return s; }
Then you consistently write:
printf("%s%s", c_str(foo), c_str(bar));
Normal C++ variadic functions cannot work with object instances because the values must be bit-copiable (the called function will not know the types of the objects passed at compile time).
With C++11 you can however write a variadic template that takes care of doing the trick of using the call-site type knowledge to unpack the parameters:
#include <string>
#include <stdio.h>
#include <string.h>
int mysprintf(char *buf, const char *fmt) {
return sprintf(buf, "%s", fmt);
}
template<typename T, typename... Args>
int mysprintf(char *buf, const char *fmt, const T& x, Args... args) {
const char *sp = strchr(fmt, '%');
if (sp) {
while (fmt < sp) *buf++ = *fmt++;
std::string fmt1(fmt, fmt+2);
int sz = sprintf(buf, fmt1.c_str(), x);
return sz + mysprintf(buf+sz, fmt+2, args...);
} else {
fputs("Invalid format string\n", stderr);
std::abort();
return 0;
}
}
template<typename... Args>
int mysprintf(char *buf, const char *fmt, const std::string& x, Args... args) {
const char *sp = strchr(fmt, '%');
if (sp) {
if (sp[1] != 's') {
fputs("Invalid format string (%s expected)\n", stderr);
std::abort();
}
while (fmt < sp) *buf++ = *fmt++;
std::string fmt1(fmt, fmt+2);
int sz = sprintf(buf, fmt1.c_str(), x.c_str());
return sz + mysprintf(buf+sz, fmt+2, args...);
} else {
fputs("Invalid format string\n", stderr);
std::abort();
return 0;
}
}
int main() {
char buf[200];
std::string foo = "foo";
const char *bar = "bar";
int x = 42;
mysprintf(buf, "foo='%s', bar='%s', x=%i", foo, bar, x);
printf("buf = \"%s\"\n", buf);
return 0;
}
NOTE: to keep things simple this code doesn't handle any formatting options or % escaping, but allows using %s for both naked char * and std::string.
NOTE2: I'm not saying this is a good idea... just that's possible