Thread: printf style output for C++

  1. #1
    C++ Junkie Mozza314's Avatar
    Join Date
    Jan 2011
    Location
    Australia
    Posts
    174

    printf style output for C++

    I've had a new enthusiasm for exploring the new features in C++11 since the standard was accepted.

    I've come across this before which shows how variadic templates can be used to make a type safe printf for C++. However, I prefer using std::cout as it is an ostream so I can use exactly same syntax when I want to do output for other ostreams.

    I came up with a system that uses this syntax:

    Code:
    std::cout << Format("three: %, a letter: %\n")(3, 'c');
    which has the output:

    Code:
    three: 3, a letter: c
    This also allows writing formatters like this:

    Code:
    Format dollarFmt("$%.00");
    std::cout << dollarFmt(187) << std::endl;
    Code:
    $187.00
    Here's the implementation:

    Code:
    #include <algorithm>
    #include <stdexcept>
    #include <cstddef>
    #include <ostream>
    #include <tuple>
    
    template <typename Tuple, std::size_t N>
    struct TuplePrinter
    {
        void operator()(std::ostream& os, const char* s, const Tuple& args) const
        {
            while (*s)
            {
                if (*s == '%')
                {
                    os << std::get<std::tuple_size<Tuple>::value - N>(args);
                    ++s;
                    TuplePrinter<Tuple, N - 1>()(os, s, args);
                    return;
                }
    
                os << *s++;
            }
    
            throw std::logic_error("Too many arguments provided to Format.");
        }
    };
    
    template <typename Tuple>
    struct TuplePrinter<Tuple, static_cast<std::size_t>(0)>
    {
        void operator()(std::ostream& os, const char* s, const Tuple& args) const
        {
            while (*s)
            {
                if (*s == '%')
                    throw std::runtime_error("Not enough arguments provided to Format.");
    
                os << *s++;
            }
        }
    };
    
    template <typename... Args>
    class FormatOutput
    {
    private:
        typedef std::tuple<Args...> Tuple;
        const char* mFormatString;
        const Tuple mTuple;
    
    
    public:
        FormatOutput(const char* formatString, Args... args)
        :
            mFormatString(formatString),
            mTuple(args...)
        {
        }
    
        void operator()(std::ostream& os) const
        {
            TuplePrinter<Tuple, std::tuple_size<Tuple>::value>()(os, mFormatString, mTuple);
        }
    };
    
    template <typename... Args>
    std::ostream& operator<<(std::ostream& os, const FormatOutput<Args...>& formatOutput)
    {
        formatOutput(os);
        return os;
    }
    
    class Format
    {
    private:
        const char* mFormatString;
    
    public:
        Format(const char* formatString)
        :
            mFormatString(formatString)
        {
        }
    
        Format(char*) = delete;
    
        template <typename... Args>
        FormatOutput<const Args&...> operator()(const Args&... args) const
        {
            return FormatOutput<const Args&...>(mFormatString, args...);
        }
    };
    Here's a demo program:

    Code:
    #include <iostream>
    
    #include "format.hpp"
    
    struct IntNotify
    {
        int mData;
    
        IntNotify(int data) : mData(data) { }
        IntNotify(const IntNotify& other) = delete;
    
        ~IntNotify()
        {
            std::cout << Format("IntNotify destructor (%)\n")(mData);
        }
    };
    
    std::ostream& operator<<(std::ostream& os, const IntNotify& intNotify)
    {
        return os << intNotify.mData;
    }
    
    int main()
    {
        std::cout << Format("int: %, char: %\n")(3, 'c');
    
        Format dollarFmt("$%");
        std::cout << dollarFmt(187) << std::endl;
    
        std::cout << Format("This is ok: %")(IntNotify(4)) << std::endl;
    
        FormatOutput<const IntNotify&> evil = Format("But don't do this: %")(IntNotify(7));
        std::cout << evil << std::endl; // undefined/possible segfault
    
        int x = 0;
        FormatOutput<const int&> good = Format("Feel free to do this: %")(x);
        x = 1;
        std::cout << good << std::endl;
    
        return 0;
    }
    Which has output:

    Code:
    int: 3, char: c
    $187
    This is ok: 4
    IntNotify destructor (4)
    IntNotify destructor (7)
    But don't do this: 7
    Feel free to do this: 1
    What I'd like is some feedback about whether you would see this as useful, and comments on my use of C++11. I'm also interested in thoughts on the use of const char* here instead of string. The way I figure it, if I use std::string, it'll have to be significantly less efficient due to the extra traverse/copy of the string (although perhaps not practically noticeable).

  2. #2
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    Well Boost Format really beat you to the punch, but I do think it's useful. Why not implement it N+1 times? Standard iostream is something you get used to but I always felt like it took up a lot of room.

  3. #3
    C++ Junkie Mozza314's Avatar
    Join Date
    Jan 2011
    Location
    Australia
    Posts
    174
    Dammit boost, lol. I should have known. I'm not sure if I like it better, I'll have to try it out sometime.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Formatting output with printf
    By Sn0wcra5h in forum C Programming
    Replies: 1
    Last Post: 01-25-2010, 03:44 PM
  2. printf output
    By shyguy24x7 in forum C Programming
    Replies: 3
    Last Post: 01-31-2009, 02:01 PM
  3. Printf Output Confusion
    By simpleid in forum C++ Programming
    Replies: 2
    Last Post: 11-16-2006, 11:36 AM
  4. why does printf output include a trailing 'D'
    By musikluvah in forum C Programming
    Replies: 5
    Last Post: 01-16-2006, 04:38 AM
  5. passing on printf()-style params?
    By ichijoji in forum Game Programming
    Replies: 1
    Last Post: 07-16-2003, 05:13 PM

Tags for this Thread