2

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?

Dzyann
  • 5,062
  • 11
  • 63
  • 95
  • std::to_string is C++11. Also, are there any known restrictions on the format of the format string? Are they always literals (quoted strings, not variables)? Do you use fancy stuff like {0,1} and {0:0} or are they all {n}? Is there a limit on the number of params? Do you repeat params like "{0} {0}"? Are there any escaped braces ("{{" and "}}")? – leewz Nov 08 '13 at 14:17
  • For now most things are like {0}, and when there is an object, I have created a custom ToString function for that object. I didn't realize to_string was of c++11, I have tried using some things of c++11 and they didnt work, so I thought I was fine. instead of that I could use my ToString function that uses ostringstream, that is old C++ no? – Dzyann Nov 08 '13 at 14:52
  • ostringstream is in C++03, yes. I'm adding to my answer. – leewz Nov 08 '13 at 22:38

3 Answers3

2

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
Dmitry Ledentsov
  • 3,620
  • 18
  • 28
  • I can not use Boost, because I don't know if I will be able to use it in all my environments. What is the Copy-Right of your library? – Dzyann Nov 08 '13 at 13:27
  • MIT license - use it, you just need to acknowledge the usage – Dmitry Ledentsov Nov 08 '13 at 13:29
  • 1
    Well, I'll upvote this, because someone with a mostly-similar situation might NOT worry as much about the license and find it useful. – leewz Nov 08 '13 at 23:08
1

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

Community
  • 1
  • 1
leewz
  • 3,201
  • 1
  • 18
  • 38
  • Are sprintf and snprintf C++ Standard? – Dzyann Nov 08 '13 at 13:34
  • sprintf is. snprintf is not (in C++ at least; might be in C), but it might be in the compiler anyway. – leewz Nov 08 '13 at 13:37
  • leewangzhong, I can't be sure yet if my compiler will have snprintf (I will check once I have access). But If I understand correctly what you say, and I use sprintf, if the length of one of my parameters is longer than the original string, it will overflow? sprintf doesnt make the original string bigger according to the size of the parameter? Does Boost have this same problem? Or Boost deals with the overflow problem? – Dzyann Nov 08 '13 at 14:07
  • sprintf is a C function. There are no objects called strings. You only have char pointers, which point to some part of memory, and functions which interpret the pointer as a string by reading the memory until they see the character \0. Boost is a collection of C++ libraries and will work with actual strings, but you won't have C# format. – leewz Nov 08 '13 at 14:13
  • I don't mind changing the format of my strings from {0} to something like %d, or whatever fits C++ better. And I know they are char points, what i mean is that they char pointer still points to the same thing, it doesnt sort of point to a new thing bigger? i have to create a buffer of the combined size and send that as recipient? – Dzyann Nov 08 '13 at 14:48
  • 1
    Pointers are pass-by-value, so changing the pointer in the function won't change the original. There would be no way to reassign the pointer in C (which doesn't have pass-by-reference). Yes, you have to create a buffer (can be statically-allocated) big enough to hold the result. If the result is too big, you get a (silent) buffer overflow. snprintf allows you to specify a limit (the size of the buffer). – leewz Nov 08 '13 at 15:58
  • thanks! I haven't realized the pointers are passed by value, of course you are right. Thanks for helping me understand all this. Just for completion, the "solution" I have so far, what do you think of it? Is it highly unefficient? (i removed the call to std::to_string and I am using ostringstream) – Dzyann Nov 08 '13 at 16:11
0

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; 
Aniket Inge
  • 25,375
  • 5
  • 50
  • 78
  • I can not do that, because in the C# code they relayed heaviliy in String::Format, and it my C++ would become rather cluttered. If I could use << inside my code and then call it from outside similar to "String.Format" of C#, I would use stringstream. I just couldn't figure out a way of doing that. Consider that in just one class you may find over 40 calls to String::Format, and it repeats a lot. That considereding some refactor I had done to remove some of the code redundance. – Dzyann Nov 08 '13 at 13:23