-1

I have an old MUD codebase in C (>80k lines) that uses printf-style string formatting. It is pervasive -- almost every bit of text runs through calls to either sprintf or a wrapper around vsprintf. However, I have recently moved to compiling with g++ to take advantage of the STL, and would like to use std::string (actually a derived class for default case-insensitive comparisons) where it makes sense.

Obviously, you can't pass std::string as one of the variadic arguments to any of the printf functions: I need .c_str() in every case. I don't want to do that, mostly because I don't want to modify 2000+ calls to printf functions. My question is: how can I make a std::string aware vsprintf?

The way I see it, I have two options: write my own printf functions that iterate through the arguments changing pointers to std::string to std::string.data (or c_out()) before passing to std::vsprintf, or I can borrow the guts of printf and roll my own. The first option sounds like less work, obviously.

Of course, a better option is if someone has done this before, but my googling is yielding nothing. Any tips on what the best option would look like?

EDIT: This question was closed as a duplicate of How to use C++ std::ostream with printf-like formatting?, which I don't believe answers the question. I'm not asking how to output strings with std::ostream vs the old C printf. I'm asking for help with a patch solution for an old C codebase that makes extensive use of sprintf/vsprintf, without rewriting thousands of calls to those functions to use output streams.

Rakurai
  • 974
  • 7
  • 15

2 Answers2

1

You can make your own printf wrapper, that extracts char const* from std::string. E.g.:

#include <iostream>
#include <string>
#include <cstdio>

template<class T>
inline auto to_c(T&& arg) -> decltype(std::forward<T>(arg)) {
    return std::forward<T>(arg);
}

inline char const* to_c(std::string const& s) { return s.c_str(); }
inline char const* to_c(std::string& s) { return s.c_str(); }

template<class... Args>
int my_printf(char const* fmt, Args&&... args) {
    return std::printf(fmt, to_c(args)...);
}

int main() {
    std::string name = "World";
    my_printf("Hello, %s!\n", name);
}

Or, better, switch to a modern C++ formatting library, such as fmt.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • YUCK! When switching to C++, take advantage of the other types C++ can format! – Jan Hudec May 22 '17 at 11:51
  • I would +1 the fmt, if it was the only thing in the answer. There is no excuse for cooking anything up with all the existing options around. – Jan Hudec May 22 '17 at 11:57
  • The fmt library looks very interesting, and I may end up using that at a later date. To solve my particular problem, the example code is working very well. I think it makes a simple building block to modernizing my code. Thanks. – Rakurai May 22 '17 at 13:34
0

The common advice is Boost.Format

Taking their example:

// printf directives's type-flag can be used to pass formatting options :
std::cout <<  format("_%1$4d_ is : _%1$#4x_, _%1$#4o_, and _%1$s_ by default\n")  % 18;
//          prints  "_  18_ is : _0x12_, _ 022_, and _18_ by default\n"

Now this assumes std::ostream&, so you'll need a std::stringstream to use a std::string as the backing buffer.

PS. using a derived class for case-insensitive comparisons sounds like a bad idea waiting to bite you. You just need a custom order; all the STL functions that assume ordering have overloads to support custom orderings.

MSalters
  • 173,980
  • 10
  • 155
  • 350