0

I have a class SpecialString. It has an operator overload / conversion function it uses any time it's passed off as a const char*. It then returns a normal c-string.

class SpecialString
{
...
operator char* () const { return mCStr; }
...
};

This used to work a long time ago (literally 19 years ago) when I passed these directly into printf(). The compiler was smart enough to know that argument was meant to be a char* and it used the conversion function, but the now g++ complains.

SpecialString str1("Hello"), str2("World");
printf("%s %s\n", str1, str2);

error: cannot pass object of non-POD type 'SPECIALSTRING' (aka 'SpecialString') through variadic method; call will abort at runtime [-Wnon-pod-varargs]

Is there any way to get this to work again without changing the code? I can add a deref operator overload function that returns the c-string and pass the SpecialString objects around like this.

class SpecialString
{
...
operator CHAR* () const { return mCStr; }
char* operator * () const { return mCStr; }
...
};
SpecialString str1("Hello"), str2("World");
printf("%s %s\n", *str1, *str2);

But I'd prefer not to because this requires manually changing thousands of lines of code.

Zero
  • 1
  • 1
  • 2
    I can almost guarantee you that "The compiler was smart enough to know that argument was meant to be a char* and it used the conversion function" is a misinterpretation of what the compiler was doing. – chris Oct 23 '19 at 02:19
  • Have you tried to compile it with option `-Wnon-pod-varargs` ? – Havenard Oct 23 '19 at 02:19
  • 2
    This never officially worked. You got away with some undefined behaviour. Sometimes you do. Sometimes you don't. – user4581301 Oct 23 '19 at 02:19
  • g++ will check your printf/sprintf arguments, if you're trying to pass a char* into a %d etc. It was just a feature compilers had back then that wasn't in the C spec. It worked on Windows Visual Studio, g++/Linux, and CodeWarrior on the Mac (pre OS X days). I think they pulled it out because it's not in the spec. – Zero Oct 23 '19 at 02:21
  • @user4581301 He's simply overloading the dereference operator, since there's no other way that dereference could be interpreted (since he is trying to dereference something that is not a pointer), I don't see how that is undefined behavior, if it compiles it should work. – Havenard Oct 23 '19 at 02:26
  • 1
    @Havenard, Passing the wrong type to `printf` is UB. – chris Oct 23 '19 at 02:27
  • @chris He's passing an expression that returns a `char*`, how is that passing the wrong type? – Havenard Oct 23 '19 at 02:31
  • Yeah @Havenard is right. I was just getting lucky that compilers back then let you do this. Was hoping there's a way to get them to do it again. Also, compiling with -Wno-non-pod-varargs aborts just like the error messages says it will. – Zero Oct 23 '19 at 02:31
  • 1
    @Havenard They're passing an lvalue of type `SpecialString`. That's the wrong type. – eerorika Oct 23 '19 at 02:31
  • The deref version works just fine, but I don't see `printf("%s %s\n", str1, str2);` causing the call operator to kick in. – user4581301 Oct 23 '19 at 02:32
  • Oh I'm sorry, I didn't read the question correctly. I thought the version with deref was the code he was using. – Havenard Oct 23 '19 at 02:33
  • Maybe `mCStr` just happens to have the same address as the object. That'll allow printf to get the `char *` it wants, but it's sorta-brittle. What's making `SpecialString` non-POD? Maybe we can attack it from another direction. – user4581301 Oct 23 '19 at 02:38

3 Answers3

0

You could disable the warning, if you don't want to be informed about it... but that's a bad idea.

The behaviour of the program is undefined, you should fix it and that requires changing the code. You can use the exising conversion operator with static_cast, or you can use your unary * operator idea, which is going to be terser.

Even less change would be required if you used unary + instead which doesn't require introducing an overload, since it will invoke the implicit conversion instead. That may add some confusion to the reader of the code though.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

Since you don't want to modify the existing code, you can write a "hack" instead. More specifically, a bunch of overloads to printf() that patch the existing code.

For example:

int printf(const char* f, const SpecialString& a, const SpecialString& b)
{
    return printf(f, (const char*)a, (const char*)b);
}

With this function declared in your header, every call to printf() with those specific parameters will use this function instead of the "real" printf() you're familiar with, and perform the needed conversions.

I presume you have quite a few combinations of printf() calls in your code envolving SpecialString, so you may have to write a bunch of different overloads, and this is ugly af to say the least but it does fit your requirement.

Havenard
  • 27,022
  • 5
  • 36
  • 62
0

As mentioned in another comment, it has always been undefined behavior that happens to work in your case.

With Microsoft CString class, it seems like the undefined behavior was so used (as it happen to work), that now the layout is defined in a way that it will still works. See How can CString be passed to format string %s?.

In our code base, I try to fix code when I modify a file to explicitly do the conversion by calling GetString()

There are a few things you could do:

  • Fix existing code everywhere you get the warning.

    In that case, a named function like c_str or GetString is preferable to a conversion operator to avoid explicit casting (for ex. static_cast or even worst C-style case (const char *). The deref operator might be an acceptable compromise.

  • Use some formatting library

  • Use variadic template function so that conversion could be done.

  • If you only use a few types (int, double, string) and rarely more than 2 or 3 parameters, defining overloads might also be a possibility.
  • Not recommended: Hack your class to works again.

    • Have you done any change to your class definition that cause it to break or only upgrade the compiler version or change compiler options?
    • Such hack is working with undefined behavior so you must figure out how your compiler works and the code won't be portable.
    • For it to works the class must have the size of a pointer and the data itself must be compatible with a pointer. Thus essentially, the data must consist of a single pointer (no v-table or other stuff).

Side note: I think that one should avoid defining its own string class. In most case, standard C++ string should be used (or string view). If you need additional functions, I would recommend your to do write stand-alone function in a namespace like StringUtilities for example. That way, you avoid converting back and forth between your own string and standard string (or some library string like MFC, Qt or something else).

Phil1970
  • 2,605
  • 2
  • 14
  • 15