Question

I have a lot of C# Code that I have to write in C++. I don't have much experience in C++.

I am using Visual Studio 2012 to build. The project is an Static Library in C++ (not in C++/CLI).

In many places they were using String.Format, like this:

C#

String.Format("Some Text {0}, some other Text {1}", parameter0, parameter1);

Now, I know similar things have been asked before, but It is not clear to me what is the most standard/safe way to do this.

Would it be safe to use something like sprintf or printf? I read some people mentioning like they are not standard. Something like this? (would this be the C++ way, or is more the C way?)

C++ (or is it C?)

char buffer [50];
int n, a=5, b=3;
n=sprintf (buffer, "Some Text %d, some other Text %d", a, b);

Other people suggested to do your own class, and I saw many different implementations.

For the time being, I have a class that uses std::to_string, ostringstream, std::string.replace and std::string.find, with Templates. My class is rather limited, but for the cases I have in the C# code, it works. Now I don't know this is the most efficient way (or even correct at all):

C++

template <typename T>
static std::string ToString(T Number)
{
    std::ostringstream stringStream;
    stringStream << Number;
    std::string string = stringStream.str();
    return string;
};

template <typename T,unsigned S> 
static std::string Format(const std::string& stringValue, const T (&parameters)[S])
{ 
    std::string stringToReturn = std::string(stringValue);

    for (int i = 0; i < S; ++i)
    {
        std::string toReplace = "{"+ std::to_string(i) +"}";
        size_t f = stringToReturn.find(toReplace);
        if(std::string::npos != f)
            stringToReturn.replace(f, toReplace.length(), ToString(parameters[i]));
    }

    return stringToReturn;      
};

//I have some other overloads that call the Format function that receives an array.
template <typename T>
    static std::string Format(const std::string& stringValue, const T parameter, const T parameter2)
    {
        T parameters[] = {parameter, parameter2};
        return Format(stringValue, parameters);
    };

And I need my code to work both in Linux and Windows, so I need different compilers to be able to build it, that is why I need to be sure I am using a standard way. And my environment can not be updated so easily, so I can not use C++11. I can not use Boost either, because I can not be sure I will be able to add the libraries in the different environments I need it to work.

What is the best approach I can take in this case?

Was it helpful?

Solution 2

sprintf works if all you have are non-object types (or you manually convert them to C-strings, or convert them to strings and then call the c_str() member function). You may want the extra protection against buffer overflow that snprintf provides.

If you're willing to learn more to do what you have to, you can use the Boost Format library. I'm sure you can write a script to convert String.format calls to Boost's syntax.

If you can't use Boost, and you can't use C++11, you have to go with sprintf and be careful about buffer overflow (possibly snprintf if you can rely on your compiler having it). You might want to write a script to wrap all the parameters so that they all convert to strings:

String.Format("Some Text {0}, some other Text {1}", to_printf(p0), to_printf(p1));

Also, note that C's format doesn't use braces. So that's a big problem. You may need to implement your own variadic function.


If everything is simple like {0}, you can probably write a script to replace most instances of String.Format (and none of the more complicated ones) with something like

`mystring = "Some Text "+tostring(p0)+", some other Text "+tostring(p1);`

which wouldn't be the most efficient way, but most likely won't matter unless you're doing thousands of formats per second. Or possibly slightly more efficient (no intermediate strings):

`"mystring = static_cast<std::ostringstream&>(std::ostringstream().flush()<<Some Text "<<p0<<", some other Text "<<p1).str();`,

which creates a temporary. The flush sort of tricks the compiler into thinking it's not a temporary, and that solves a specific problem about not being able to use non-member operator<<.

OTHER TIPS

Here's a 1-header library I've been writing just for that purpose: fakeformat

Test:

REQUIRE(ff::format("{2}ff{1}").with('a').also_with(7).now()=="7ffa");

The library is configurable, so that you can start parameter indexing from 0. You can also write a wrapper, so that it would look exactly like String.Format.

It builds on linux and doesn't need c++11.

There's no standard way yet...

Or, you could use Boost.Locale formatting

Here it is, with indices starting from 0:

#include ...

struct dotnet_config {
  static const char scope_begin='{';
  static const char scope_end='}';
  static const char separator=',';
  static const char equals='=';
  static const size_t index_begin=0;
  static bool string_to_key(std::string const& to_parse,int& res) {
    std::istringstream ss(to_parse);
    ss.imbue(std::locale::classic());
    ss >> res;
    if (!ss.fail() && ss.eof())
      return true;
    return false;
  }
};

template <typename T1>
std::string Format (std::string const& format_string,T1 p1) {
 return ff::formatter<dotnet_config>(format_string).with(p1).now();
}

template <typename T1,typename T2>
std::string Format (std::string const& format_string,T1 p1,T2 p2) {
 return ff::formatter<dotnet_config>(format_string).with(p1).with(p2).now();
}

int main() {
  std::cout<<Format("test={0}",42)<<std::endl;
  std::cout<<Format("{0}!={1}",33,42)<<std::endl;
  return 0;
}

Output:

test=42
33!=42

Why don't you use the << operator to format your string?

string strOutput;
stringstream strn;
int i = 10;
float f = 20.0f;
strn << "Sally scored "<<i<< " out of "<<f << ". She failed the test!";
strn >> strOutput;
cout << strOutput; 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top