0

How to detect when I am passing a std::string, instead of a c_str() to my variadic function?

My initial problem is that when I do pass a std::string, instead of a c_str() i.e., std::string s.c_str(), to my variadic function, my program is going if I run if from shell script on my Sublime Text build. This is what is happening, if I pass a std::string and try to run if from Sublime Text:

rm -f main.exe
g++ --std=c++11 main.cpp -I . -o main
Starting the main program...

[Finished in 3.4s]

But if I run from a shell with ./main, everything is OK, except the string print:

Starting the main program...

argumentsCount: 3
argumentsStringList[0]: ./main
argumentsStringList[1]: 1
argumentsStringList[2]: 2

SourceCode::SourceCode(1) text_code: ▒
Exiting main(2)

This is my function which do the parsing:

/**
 * Missing string printf. This is safe and convenient but not exactly efficient.
 *
 * @param fmt     a char array
 * @param ...     a variable length number of formating characters.
 *
 * @see http://stackoverflow.com/a/10150393/4934640
 * @see http://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/10150393#10150393
 */
inline std::string format(const char* fmt, ...)
{
    int   size   = 512;
    char* buffer = new char[size];

    va_list var_args;
    va_start(var_args, fmt);

    int nsize = vsnprintf(buffer, size, fmt, var_args);

    //fail delete buffer and try again
    if(size<=nsize)
    {
        delete[] buffer;

        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize  = vsnprintf(buffer, size, fmt, var_args);
    }

    std::string ret(buffer);
    va_end(var_args);

    delete[] buffer;
    return ret;
}

This is my tentative. It is a minimal practical example, you can run it on your own computer compiling with g++ --std=c++11 main.cpp -o main:

#include <cstdlib>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <cstdarg>
#include <typeinfo>

inline std::string format(const char* fmt, ...)
{
    int   size   = 512;
    char* buffer = new char[size];

    std::string* temp;
    va_list var_args_copy;

    printf( "%s\n", fmt );
    va_start(var_args_copy, fmt);

    do
    {
        temp = va_arg( var_args_copy, std::string* );

        if( typeid( temp ) == typeid( std::string* ) )
        {
            std::string message( "ERROR!!!!!!!!!!!!!! Bad formatted string!\n" );
            return message;
        }

    } while( temp != NULL );

    va_list var_args;
    va_start(var_args, fmt);

    int nsize = vsnprintf(buffer, size, fmt, var_args);

    //fail delete buffer and try again
    if(size<=nsize)
    {
        delete[] buffer;

        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize  = vsnprintf(buffer, size, fmt, var_args);
    }

    std::string ret(buffer);
    va_end(var_args);

    delete[] buffer;
    return ret;
}

int main( int argumentsCount, char* argumentsStringList[] )
{
    printf( "" );

    std::string strin( "String" );

    std::cout << format( "1. The string is %s", strin ) << std::endl;
    std::cout << format( "2. The string is %s", strin.c_str() ) << std::endl;

    printf( "Exiting main(2)" );
    return EXIT_SUCCESS;
}

Running my tentative, it outputs me this:

g++ -std=c++11 main2.cpp -I . -o main
1. The string is %s
ERROR!!!!!!!!!!!!!! Bad formatted string!

2. The string is %s
ERROR!!!!!!!!!!!!!! Bad formatted string!

Exiting main(2)[Finished in 3.7s]

Here the on the 2. The string is %s, the ERROR!!!!!!!!!!!!!! Bad formatted string! must not to be printed because it is not a std::string. What is wrong about the typeid( temp ) == typeid( std::string* ) letting it pass by?

Anyways, another possible solution could be just accept std::string instead of a c_str() i.e., std::string s.c_str().

Evandro Coan
  • 8,560
  • 11
  • 83
  • 144
  • 3
    That's the thing about C variadics. They're pretty unsafe. Use a variadic template if you want type safety. – chris Feb 21 '17 at 01:43
  • 2
    You can't do this with crufty C-style varargs. There's nothing about a some random chunk of memory that says "I am a `std::string`, and I am a c_str()". If you want to do this correctly, use proper C++ templates. – Sam Varshavchik Feb 21 '17 at 01:43

1 Answers1

0

I find out a third part library (include), which uses variadic templates, solving the problem.

  1. https://github.com/c42f/tinyformat

This is the code now:

#include <cstdlib>
#include "libraries/tinyformat/tinyformat.h"

int main( int argumentsCount, char* argumentsStringList[] )
{
    printf( "" );

    std::string strin( "String" );

    std::cout << tfm::format( "1. The string is %s", strin ) << std::endl;
    std::cout << tfm::format( "2. The string is %s", strin.c_str() ) << std::endl;

    printf( "Exiting main(2)\n" );
    return EXIT_SUCCESS;
}

Which just outputs correctly:

rm -f main.exe
g++ -std=c++11 main2.cpp -I . -o main
1. The string is String
2. The string is String
Exiting main(2)
[Finished in 4.3s]
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144