Because I don't want to download an entire library for a single function (which I do understand, was made better than I made mine), I decided to implement my own string formatting function.
I am not very proud of it, as I find it pretty ugly and unreadable.
This is only a string
formatter with string
-only arguments.
Efficiency is not a concern for me.
#include<iostream>
#include<unordered_map>
#include<string>
using std::string;
string formatString(string format, const std::unordered_map<string, string>& args) {
string ret;
string::size_type bracketLoc;
while((bracketLoc = format.find_first_of('{')) != string::npos) {
// Handling the escape character.
if(bracketLoc > 0 && format[bracketLoc - 1] == '\\') {
ret += format.substr(0, bracketLoc + 1);
format = format.substr(bracketLoc + 1);
continue;
}
ret += format.substr(0, bracketLoc);
format = format.substr(bracketLoc + 1);
bracketLoc = format.find_first_of('}');
string arg = format.substr(0, bracketLoc);
format = format.substr(bracketLoc + 1);
auto it = args.find(arg);
if(it == args.end()) {
ret += "(nil)";
} else {
ret += it->second;
}
}
ret += format;
return ret;
}
int main() {
std::cout << formatString("Hello, {Name}! {WeatherType} weather, right?", {
{"Name", "Midnightas"},
{"Fruit", "Apple"}
});
return 0;
}
Compile with -std=c++11
.
The above program will output Hello, Midnightas! (nil) weather, right?
.
1 Answer 1
You have a problem if you want to have the escape-character immediately before a replacement, because you don't support escaping the escape-character.
I suggest dispensing with escape-characters, and simply replace an empty replacement{}
with an opening brace{
.There's no reason to modify the format-string at all, and thus receiving it by copy. Doing so is supremely inefficient. Well, using named arguments at all already is, so it might not matter too much.
Do you know the ternary operator
cond ? true_exp : false_exp
? Using it would simplify things.Avoid allocations, thus avoid
std::string
. At least the format string should be C++17std::string_view
, maybe also the map should be too.
If you template it, you don't even really have to decide for the caller:template <class Args = std::unordered_map<std::string_view, std::string_view>> std::string formatString(std::string_view format, const Args& args)
-
\$\begingroup\$ Where would the ternary operator help here, other than when checking the existence of an argument? \$\endgroup\$mid– mid2018年05月26日 18:12:33 +00:00Commented May 26, 2018 at 18:12
-
\$\begingroup\$ Just as you said, at the end of the loop when deciding what to add. \$\endgroup\$Deduplicator– Deduplicator2018年05月26日 18:15:07 +00:00Commented May 26, 2018 at 18:15
-
\$\begingroup\$ Some things I can't do here since I'm using C++11, but thanks! \$\endgroup\$mid– mid2018年05月26日 19:08:43 +00:00Commented May 26, 2018 at 19:08
"This is %2 of %1\n"
, where%2
is replaced with the second argument and%1
is replaced with the first argument. Not sure if named arguments helps that much. \$\endgroup\$