grunt
grunt

Reputation: 712

C++ & the C arg_list functions

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.

Upvotes: 1

Views: 572

Answers (3)

firda
firda

Reputation: 3338

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).

Upvotes: 2

6502
6502

Reputation: 114569

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

Upvotes: 1

mrjoltcola
mrjoltcola

Reputation: 20852

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));

Upvotes: 1

Related Questions