std::string formatting like sprintf

前端 未结 30 2548
野趣味
野趣味 2020-11-22 04:42

I have to format std::string with sprintf and send it into file stream. How can I do this?

相关标签:
30条回答
  • 2020-11-22 05:02

    [edit: 20/05/25] better still...:
    In header:

    // `say` prints the values
    // `says` returns a string instead of printing
    // `sayss` appends the values to it's first argument instead of printing
    // `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>
    
    void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
    template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
    template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
    template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
    template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }
    

    The PRINTSTRING(r)-function is to cater for GUI or terminal or any special output needs using #ifdef _some_flag_, the default is:

    void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }
    

    [edit '17/8/31] Adding a variadic templated version 'vtspf(..)':

    template<typename T> const std::string type_to_string(const T &v)
    {
        std::ostringstream ss;
        ss << v;
        return ss.str();
    };
    
    template<typename T> const T string_to_type(const std::string &str)
    {
        std::istringstream ss(str);
        T ret;
        ss >> ret;
        return ret;
    };
    
    template<typename...P> void vtspf_priv(std::string &s) {}
    
    template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
    {
        s+=type_to_string(h);
        vtspf_priv(s, p...);
    }
    
    template<typename...P> std::string temp_vtspf(P...p)
    {
        std::string s("");
        vtspf_priv(s, p...);
        return s;
    }
    

    which is effectively a comma-delimited version (instead) of the sometimes hindering <<-operators, used like this:

    char chSpace=' ';
    double pi=3.1415;
    std::string sWorld="World", str_var;
    str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);
    


    [edit] Adapted to make use of the technique in Erik Aronesty's answer (above):

    #include <string>
    #include <cstdarg>
    #include <cstdio>
    
    //=============================================================================
    void spf(std::string &s, const std::string fmt, ...)
    {
        int n, size=100;
        bool b=false;
        va_list marker;
    
        while (!b)
        {
            s.resize(size);
            va_start(marker, fmt);
            n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
            va_end(marker);
            if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
        }
    }
    
    //=============================================================================
    void spfa(std::string &s, const std::string fmt, ...)
    {
        std::string ss;
        int n, size=100;
        bool b=false;
        va_list marker;
    
        while (!b)
        {
            ss.resize(size);
            va_start(marker, fmt);
            n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
            va_end(marker);
            if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
        }
        s += ss;
    }
    

    [previous answer]
    A very late answer, but for those who, like me, do like the 'sprintf'-way: I've written and are using the following functions. If you like it, you can expand the %-options to more closely fit the sprintf ones; the ones in there currently are sufficient for my needs. You use stringf() and stringfappend() same as you would sprintf. Just remember that the parameters for ... must be POD types.

    //=============================================================================
    void DoFormatting(std::string& sF, const char* sformat, va_list marker)
    {
        char *s, ch=0;
        int n, i=0, m;
        long l;
        double d;
        std::string sf = sformat;
        std::stringstream ss;
    
        m = sf.length();
        while (i<m)
        {
            ch = sf.at(i);
            if (ch == '%')
            {
                i++;
                if (i<m)
                {
                    ch = sf.at(i);
                    switch(ch)
                    {
                        case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                        case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                        case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                        case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                        case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                        case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                        case 'X':
                        case 'x':
                            {
                                if (++i<m)
                                {
                                    ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                    if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                    char ch2 = sf.at(i);
                                    if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                    else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                    else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                    else ss << '%' << ch << ch2;
                                    ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                                }
                            } break;
                        case '%': { ss << '%'; } break;
                        default:
                        {
                            ss << "%" << ch;
                            //i = m; //get out of loop
                        }
                    }
                }
            }
            else ss << ch;
            i++;
        }
        va_end(marker);
        sF = ss.str();
    }
    
    //=============================================================================
    void stringf(string& stgt,const char *sformat, ... )
    {
        va_list marker;
        va_start(marker, sformat);
        DoFormatting(stgt, sformat, marker);
    }
    
    //=============================================================================
    void stringfappend(string& stgt,const char *sformat, ... )
    {
        string sF = "";
        va_list marker;
        va_start(marker, sformat);
        DoFormatting(sF, sformat, marker);
        stgt += sF;
    }
    
    0 讨论(0)
  • 2020-11-22 05:03

    Modern C++ makes this super simple.

    C++20

    C++20 introduces std::format, which allows you to do exactly that. It uses replacement fields similar to those in python:

    #include <iostream>
    #include <format>
     
    int main() {
        std::cout << std::format("Hello {}!\n", "world");
    }
    

    Check out the compiler support page to see if it's available in your standard library implementation. As of 2020-11-06, it's not supported by any, so you'll have to resort to the C++11 solution below.


    C++11

    With C++11s std::snprintf, this already became a pretty easy and safe task.

    #include <memory>
    #include <string>
    #include <stdexcept>
    
    template<typename ... Args>
    std::string string_format( const std::string& format, Args ... args )
    {
        size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
        if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
        std::unique_ptr<char[]> buf( new char[ size ] ); 
        snprintf( buf.get(), size, format.c_str(), args ... );
        return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
    }
    

    The code snippet above is licensed under CC0 1.0.

    Line by line explanation:

    Aim: Write to a char* by using std::snprintf and then convert that to a std::string.

    First, we determine the desired length of the char array using a special condition in snprintf. From cppreference.com:

    Return value

    [...] If the resulting string gets truncated due to buf_size limit, function returns the total number of characters (not including the terminating null-byte) which would have been written, if the limit was not imposed.

    This means that the desired size is the number of characters plus one, so that the null-terminator will sit after all other characters and that it can be cut off by the string constructor again. This issue was explained by @alexk7 in the comments.

    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;
    

    snprintf will return a negative number if an error occurred, so we then check whether the formatting worked as desired. Not doing this could lead to silent errors or the allocation of a huge buffer, as pointed out by @ead in the comments.

    if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    

    Next, we allocate a new character array and assign it to a std::unique_ptr. This is generally advised, as you won't have to manually delete it again.

    Note that this is not a safe way to allocate a unique_ptr with user-defined types as you can not deallocate the memory if the constructor throws an exception!

    std::unique_ptr<char[]> buf( new char[ size ] );
    

    After that, we can of course just use snprintf for its intended use and write the formatted string to the char[].

    snprintf( buf.get(), size, format.c_str(), args ... );
    

    Finally, we create and return a new std::string from that, making sure to omit the null-terminator at the end.

    return std::string( buf.get(), buf.get() + size - 1 );
    

    You can see an example in action here.


    If you also want to use std::string in the argument list, take a look at this gist.


    Additional information for Visual Studio users:

    As explained in this answer, Microsoft renamed std::snprintf to _snprintf (yes, without std::). MS further set it as deprecated and advises to use _snprintf_s instead, however _snprintf_s won't accept the buffer to be zero or smaller than the formatted output and will not calculate the outputs length if that occurs. So in order to get rid of the deprecation warnings during compilation, you can insert the following line at the top of the file which contains the use of _snprintf:

    #pragma warning(disable : 4996)
    

    Final thoughts

    A lot of answers to this question were written before the time of C++11 and use fixed buffer lengths or vargs. Unless you're stuck with old versions of C++, I wouldn't recommend using those solutions. Ideally, go the C++20 way.

    Because the C++11 solution in this answer uses templates, it can generate quite a bit of code if it is used a lot. However, unless you're developing for an environment with very limited space for binaries, this won't be a problem and is still a vast improvement over the other solutions in both clarity and security.

    If space efficiency is super important, these two solution with vargs and vsnprintf can be useful. DO NOT USE any solutions with fixed buffer lengths, that is just asking for trouble.

    0 讨论(0)
  • 2020-11-22 05:04
    _return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
    
    0 讨论(0)
  • 2020-11-22 05:05

    If you only want a printf-like syntax (without calling printf yourself), have a look at Boost Format.

    0 讨论(0)
  • 2020-11-22 05:05

    C++20 std::format

    It has arrived! The feature is described at: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html and uses a Python-like .format() syntax.

    I expect that the usage will be like:

    #include <format>
    #include <string>
    
    int main() {
        std::string message = std::format("The answer is {}.", 42);
    }
    

    I'll give it a try when support arrives to GCC, GCC 9.1.0 with g++-9 -std=c++2a still doesn't support it.

    The API will add a new std::format header:

    The proposed formatting API is defined in the new header <format> and should have no impact on existing code.

    The existing fmt library claims to implement it if you need the polyfill: https://github.com/fmtlib/fmt

    Implementation of C++20 std::format.

    and was previously mentioned at: std::string formatting like sprintf

    Hexadecimal format {:x}

    C++ cout hex values?

    Leading zeroes {:03}

    Print leading zeros with C++ output operator?

    Alignment left {:<}, right {:>}, center {:^}

    C++ alignment when printing cout <<

    Floating point precision {:.2}

    Set back default floating point print precision in C++

    Show sign on positive numbers {:+}

    How to print positive numbers with a prefix + in C++

    Show booleans as true and false: {:}

    Converting bool to text in C++

    0 讨论(0)
  • 2020-11-22 05:06

    Very-very simple solution.

    std::string strBuf;
    strBuf.resize(256);
    int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
    strBuf.resize(iCharsPrinted);
    
    0 讨论(0)
提交回复
热议问题