11
\$\begingroup\$

I've never been satisfied with any of the possible sprintf()-like functions in C++:

  • sprintf() is a C function and it's very unsafe and dangerous to use (undefined behavior all over the place)
  • boost::format is too slow and awkward to use (think of the % syntax)
  • iostream is extremely awkward to use

So I came up with my own. Those are my requirements:

  • Inspired by C#'s format function
  • Must never fail at run time, never crash and never have undefined behavior, no matter what you call it with. Should fail at compile time whenever possible instead.
  • Must be expandable (if you create a class Person, then you can pass a Person object to the function)
  • Must be fast
  • The format string must be easy to localize
  • The format string must not have placeholders whose syntax depends on the argument type (ie, no %s vs %d)
  • Must have a fairly "normal" syntax (no % like boost::format)

I'm interested in reviews on the satisfaction of the requirements above. I'm especially interested in reviews from the end-user perspective and less about the internals of the code (i.e., the code that is hidden from the end user).

A few examples:

// Returns a string containing "Hello my name is Andreas and I'm 22 years old".
Format("Hello my name is {0} and I'm {1} years old.", "Andreas", 22);

// Returns "Hex: 0xA"
Format("Hex: {0:x}", 10);

// Fails to compile: Person is not a built in type and doesn't have any function to convert to string
struct Person {};
Person p;
Format("Person: {0}", p);

// "Person: Andreas Bonini" [Note: if it was {0:abc}, then Person::ToString("abc") would have been called]
struct Person {
   Person(string first, string last) : First(first), Last(last) {}
   string ToString(const string &options) const { return Format("{0} {1}", First, Last); }
   string First, Last;
};
Person p("Andreas", "Bonini");
Format("Person: {0}", p);

These are my unit tests:

TEST(Format)
{
    CHECK_EQUAL(Format("Testing {0} test {0}{0} test", 123), "Testing 123 test 123123 test");
    CHECK_EQUAL(Format("{0}", (const char *)NULL), "{null}");

    CHECK_EQUAL(Format("Test double {0} - {1}", 0, 1), "Test double 0 - 1");
    CHECK_EQUAL(Format("Test options: {0:x}", 0xABC), "Test options: 0xABC");
    CHECK_EQUAL(Format("Test malformed: {0:x", 0xABC), "Test malformed: {0:x");
    CHECK_EQUAL(Format("Check stupid: {0:bmkldmbkglmbgk902 r iko4om wkl lfs s,gf, gfsdg fsd ! @ G}", "stupid"), "Check stupid: stupid");
    CHECK_EQUAL(Format("Check missing: {1}", 0), "Check missing: {Argument 1}");

    CHECK_EQUAL(Format("Master format test {0} {1} {2 {2:2} {3} {4:e} {5:x} {0}", 0, 1234.55566f, 1.11111f, "lolz", "a'b", 0xABCDEFABCDEFULL),
        "Master format test 0 1234.56 {2 1.11 lolz 'a\\'b' 0xABCDEFABCDEF 0");

    CHECK_EQUAL(Format("{0:x}", 0xFFFFFFFF), "0xFFFFFFFF");
}

Note that due to C++ limitations (lack of variadic templates) the code isn't exactly pretty. Also the code is part of a bigger library so it won't compile of its own.

/****************************************************************************************
 * Prism
 * File Name: Format.h
 * Added on: 2010/09/26 14:09:52
 */

/***************************************************************************************/

#pragma once
#include <boost/type_traits.hpp>

/***************************************************************************************/

/* This is the main formatting function; it is 100% type safe and hopefully fast. It's inspired
 * by C#'s String.Format() function and has pretty much the base syntax:
 *
 * string format = Format("Hello {0}, my name is {1}.", "you", "me");
 *
 * More precisely the placeholder can be:
 * {n[,a][:o]}
 *
 * n = Number of argument, starting from 0
 * a = Alignment; if it's positive it is right aligned, otherwise left aligned
 * o = Options
 *
 * The arguments themselves to the conversion and handle the options. For each passed argument the
 * .ToString(options) function will be called, and the returned value will be used for replacing the
 * placeholder.
 *
 * Of course this won't work with built in types such as int. The conversion for those is implemented
 * in the library, which supports the following types (and options).
 *
 * The 'a' option means alternative.
 *
 * -- bool --
 * - No options: prints "true" or "false".
 * - a: prints "yes" or "no".
 *
 * -- char --
 * - No options: prints the character normally
 * - a: prints the character as a number.
 *
 * -- short, int, long --
 * - No options: prints the number normally
 * - b: prints the number as binary
 * - x: prints the number as hex, prepending "0x"
 * - X: prints the number as hex, without "0x"
 * - ,: prints the number with a comma as thousands separator
 *
 * -- float, double --
 * - No options: prints the number normally
 * - A number (N): equivalent to %.Nf
 * - %: multiplies by 100, adds a % sign
 *
 * -- string --
 * - No options: prints the string normally
 * - e: Escapes the string for use in MySQL queries.
 *
 * >>> Error handling <<<
 * - Not enough arguments: FATAL() is called
 * - Too many arguments: not an error; extra arguments are ignored
 * - Unknown options: not an error; unknown options may not be implemented yet
 */

/***************************************************************************************/

namespace Prism
{
namespace Text
{
namespace Conversion
{

template<class T>
string IntToString(T _val, bool hasSign, const string &options)
{
    typename boost::make_signed<T>::type valSigned = _val;
    typename boost::make_unsigned<T>::type valUnsigned = _val;

    if(options == ",")
        return GetCommaNumber(hasSign ? valSigned : valUnsigned);

    static const char itoa_tab[10] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'  };
    static const char xtoa_tab[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    static const char btoa_tab[2] = { '0', '1' };

    int base;
    string ret;
    const char *tab;
    char tmp[128];
    char *ptr = &tmp[sizeof(tmp) - 1];
    *ptr = 0;

    /* Parse options, set up variables */
    if (options == "b") {
        base = 2;
        ret.reserve(64 + 1);
        hasSign = false;
        tab = btoa_tab;
    } else if (options == "x") {
        base = 16;
        ret.reserve(16 + 2 + 1);
        ret.push_back('0');
        ret.push_back('x');
        hasSign = false;
        tab = xtoa_tab;
    } else if (options == "X") {
        base = 16;
        ret.reserve(16 + 2);
        hasSign = false;
        tab = xtoa_tab;
    } else {
        base = 10;
        ret.reserve(22);
        tab = itoa_tab;
    }

    /* If it's negative */
    if(hasSign && valSigned < 0)
    {
        ret.push_back('-');
        valSigned = Math::Abs(valSigned);
        valUnsigned = valSigned;
    }

    do {
        *--ptr = tab[valUnsigned % base];
        valUnsigned /= base;
    } while (valUnsigned);

    while(*ptr)
        ret.push_back(*ptr++);

    return ret;
}

inline string ToString(const char *s, const string &options)
{
    if(!s)
        s = "{null}";

    if(options == "e")
        return SqlSafeCopy(s);
    return s;
}

inline string ToString(char *s, const string &options)
{
    return ToString((const char *)s, options);
}

template<typename T>
string ToString(const T &val, const string &options) { return val.ToString(options); }

template<> string ToString(const char &val, const string &options);
template<> string ToString(const bool &val, const string &options);
template<> string ToString(const float &val, const string &options);
template<> string ToString(const double &val, const string &options);

template<> inline string ToString(const int64 &val, const string &options) { return IntToString(val, true, options); }
template<> inline string ToString(const uint64 &val, const string &options) { return IntToString(val, false, options); }
template<> inline string ToString(const int32 &val, const string &options) { return IntToString(val, true, options); }
template<> inline string ToString(const uint32 &val, const string &options) { return IntToString(val, false, options); }
template<> inline string ToString(const int16 &val, const string &options) { return IntToString(val, true, options); }
template<> inline string ToString(const uint16 &val, const string &options) { return IntToString(val, false, options); }
template<> inline string ToString(const float &val, const string &options) { return ToString(static_cast<double>(val), options); }

template<> inline string ToString(const string &s, const string &options)
{
    if(options == "e")
        return SqlSafeCopy(s);
    return s;
}

}
}
}

/***************************************************************************************/

namespace Prism
{
namespace Text
{

struct PlaceholderData
{
    PlaceholderData(int start, int end, const string &argument, int &final_length) : Start(start), End(end), Argument(argument)
    {
        final_length = final_length - (End - Start) + Argument.length();
    }

    int Start;
    int End;
    string Argument;
};

extern string _FinalizeFormat(const string &str, const vector<PlaceholderData> &placeholders, int final_length);
extern bool _FindPlaceholder(const char *str, int &start, const int &end, int &num, int &placeholder_length, string &options);

#include "FormatNoVariadicTemplates.h"
}
}

/***************************************************************************************/
/****************************************************************************************
 * Prism
 * File Name: Format.cpp
 * Added on: 2010/09/26 14:56:35
 */

/***************************************************************************************/

#include "Prism.h"
#include "Format.h"
#include "Text.h"

#if COMPILER == COMPILER_MSVC
#pragma warning (disable: 4996)
#endif

namespace Prism
{
namespace Text
{

/***************************************************************************************/

namespace Conversion
{
    template<>
    string ToString(const char &val, const string &options)
    {
        const bool alternative = (options == "a");

        if (!alternative)
            return string(1, val);
        else
            return ToString((int)val, "");
    }

    template<>
    string ToString(const bool &val, const string &options)
    {
        static const char *normalStrings[] = { "true", "false" };
        static const char *alternativeStrings[] = { "yes", "no" };
        const bool alternative = (options == "a");
        const char **strings = alternative ? alternativeStrings : normalStrings;

        if (val)
            return strings[0];
        else
            return strings[1];
    }

    template<>
    string ToString(const double &val, const string &options)
    {
        if (options[0] == '%')
            return ToString(val * 100, options.substr(1)) + "%";

        char ret[128];
        if (IsDigit(options[0]) && !options[1]) {
            char format[5];
            format[0] = '%';
            format[1] = '.';
            format[2] = options[0];
            format[3] = 'f';
            format[4] = '\0';

            sprintf(ret, format, val);
        } else
            sprintf(ret, "%g", val);

        return ret;
    }
}

/***************************************************************************************/

string _FinalizeFormat(const string &str, const vector<PlaceholderData> &placeholders, int final_length)
{
    if(placeholders.empty())
        return str;

    string ret;
    ret.reserve(final_length);

    int current_position = 0;
    const char *ptr = str.data();

    foreach (const PlaceholderData &placeholder, placeholders) {
        if (placeholder.Start != current_position) {
            const int bytes = (placeholder.Start - current_position);
            ret.append(ptr + current_position, bytes);
            current_position += bytes;
        }

        ret.append(placeholder.Argument);
        current_position += (placeholder.End - placeholder.Start);
    }

    const int remaining_bytes = str.size() - current_position;

    if (remaining_bytes > 0)
        ret.append(ptr + current_position, remaining_bytes);

    return ret;
}

/***************************************************************************************/

static bool ReadPlaceholder(const char *str, int &start, const int &end, int &num, int &placeholder_length, string &options)
{
    const char *placeholder_number = &str[start + 1];
    char *endStr;

    if (!IsDigit(*placeholder_number))
        return false;

    num = strtoul(placeholder_number, &endStr, 10);

    if (!endStr)
        return false;

    if (*endStr == ':') {
        /* Read options */
        ++endStr;

        const char *begin = endStr;
        while (endStr < &str[end] && *endStr != '}')
            ++endStr;

        if (endStr == &str[end])
            return false;

        options.assign(begin, endStr - begin);
    } else
        options = "";

    if (*endStr != '}')
        return false;

    ++endStr;
    placeholder_length = endStr - &str[start];
    return true;
}

/***************************************************************************************/

bool _FindPlaceholder(const char *str, int &start, const int &end, int &num, int &placeholder_length, string &options)
{
    /* Find the '{' */
    for (int i = start; i < end; ++i)
    {
        if (str[i] != '{')
            continue;

        /* Check if there is enough space for the minimum spaceholder length, 3: {.} */
        if (end - i < 3)
            continue;

        if (ReadPlaceholder(str, i, end, num, placeholder_length, options))
        {
            start = i;
            return true;
        }
    }

    /* If we reach here, we reached the end of the string */
    start = end;
    return false;
}

/***************************************************************************************/

}
}
/****************************************************************************************
 * Prism
 * File Name: FormatNoVariadicTemplates.h
 * Added on: 2010/09/26 21:04:33
 */

/***************************************************************************************/

#pragma once

/***************************************************************************************/

/* Here we go.. */

#define FNVT_BEGIN vector<PlaceholderData> placeholders; \
    int final_length = str.length(); \
    int start = 0; \
    const int end = str.length(); \
    int num, placeholder_length; \
    string argument, options; \
    while (start < end) { \
        if (!_FindPlaceholder(str.c_str(), start, end, num, placeholder_length, options)) \
            continue; \
        switch(num) {

#define FNVT_END default: \
            argument = Format("{Argument {0}}", num); \
            break; \
        } \
        placeholders.push_back(PlaceholderData(start, start + placeholder_length, argument, final_length)); \
        start += placeholder_length; \
    } \
    return _FinalizeFormat(str, placeholders, final_length);

#define ARGUMENT(n) case n: \
    argument = Conversion::ToString(_ ## n, options); \
    break; \

/***************************************************************************************/

template<typename A>
string Format(const string &str, const A &_0)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    FNVT_END;
}


template<typename A, typename B>
string Format(const string &str, const A &_0, const B &_1)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    FNVT_END;
}


template<typename A, typename B, typename C>
string Format(const string &str, const A &_0, const B &_1, const C &_2)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R, typename S>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17, const S &_18)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    ARGUMENT(18);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R, typename S, typename T>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17, const S &_18, const T &_19)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    ARGUMENT(18);
    ARGUMENT(19);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R, typename S, typename T, typename U>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17, const S &_18, const T &_19, const U &_20)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    ARGUMENT(18);
    ARGUMENT(19);
    ARGUMENT(20);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R, typename S, typename T, typename U, typename V>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17, const S &_18, const T &_19, const U &_20, const V &_21)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    ARGUMENT(18);
    ARGUMENT(19);
    ARGUMENT(20);
    ARGUMENT(21);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R, typename S, typename T, typename U, typename V, typename W>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17, const S &_18, const T &_19, const U &_20, const V &_21, const W &_22)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    ARGUMENT(18);
    ARGUMENT(19);
    ARGUMENT(20);
    ARGUMENT(21);
    ARGUMENT(22);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R, typename S, typename T, typename U, typename V, typename W, typename X>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17, const S &_18, const T &_19, const U &_20, const V &_21, const W &_22, const X &_23)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    ARGUMENT(18);
    ARGUMENT(19);
    ARGUMENT(20);
    ARGUMENT(21);
    ARGUMENT(22);
    ARGUMENT(23);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R, typename S, typename T, typename U, typename V, typename W, typename X, typename Y>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17, const S &_18, const T &_19, const U &_20, const V &_21, const W &_22, const X &_23, const Y &_24)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    ARGUMENT(18);
    ARGUMENT(19);
    ARGUMENT(20);
    ARGUMENT(21);
    ARGUMENT(22);
    ARGUMENT(23);
    ARGUMENT(24);
    FNVT_END;
}


template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J, typename K, typename L, typename M, typename N, typename O, typename P, typename Q, typename R, typename S, typename T, typename U, typename V, typename W, typename X, typename Y, typename Z>
string Format(const string &str, const A &_0, const B &_1, const C &_2, const D &_3, const E &_4, const F &_5, const G &_6, const H &_7, const I &_8, const J &_9, const K &_10, const L &_11, const M &_12, const N &_13, const O &_14, const P &_15, const Q &_16, const R &_17, const S &_18, const T &_19, const U &_20, const V &_21, const W &_22, const X &_23, const Y &_24, const Z &_25)
{
    FNVT_BEGIN;
    ARGUMENT(0);
    ARGUMENT(1);
    ARGUMENT(2);
    ARGUMENT(3);
    ARGUMENT(4);
    ARGUMENT(5);
    ARGUMENT(6);
    ARGUMENT(7);
    ARGUMENT(8);
    ARGUMENT(9);
    ARGUMENT(10);
    ARGUMENT(11);
    ARGUMENT(12);
    ARGUMENT(13);
    ARGUMENT(14);
    ARGUMENT(15);
    ARGUMENT(16);
    ARGUMENT(17);
    ARGUMENT(18);
    ARGUMENT(19);
    ARGUMENT(20);
    ARGUMENT(21);
    ARGUMENT(22);
    ARGUMENT(23);
    ARGUMENT(24);
    ARGUMENT(25);
    FNVT_END;
}

/***************************************************************************************/
\$\endgroup\$
  • \$\begingroup\$ PS: comments in the code talk about alignment, the {0,10} syntax, which is not implemented yet. Also I wrote my examples (but not the unit tests) without trying to run them first. \$\endgroup\$ – Thomas Bonini Apr 30 '11 at 14:26
  • \$\begingroup\$ You might be interested in the C++ Format library cppformat.github.io. It is based on Python's str.format which uses very similar format string syntax. It is also very fast, depending on format string it can even be faster than sprintf. \$\endgroup\$ – vitaut May 20 '14 at 0:30
10
\$\begingroup\$

There are some serious portability-issues with the code:

  • You do not have proper header guards. #pragma is non-standard.
  • You do not have proper includes (because of a precompiled header, I presume).
  • Names beginning with an underscore followed by an uppercase letter (e.g. _FinalizeFormat, _FindPlaceholder) are reserved by the compiler.
\$\endgroup\$
  • 1
    \$\begingroup\$ For first point i use to #ifndef LIKE_THIS #define LIKE_THIS ... #endif // LIKE_THIS \$\endgroup\$ – siquell May 2 '11 at 12:57
  • \$\begingroup\$ @siquell Ditto :) \$\endgroup\$ – Lstor Dec 29 '11 at 20:01
7
\$\begingroup\$

In my opinion:

  • Use fullscope names (like std::vector, boost::shared_ptr)
  • Don't use const & for simple types (for example int or double)
  • Write const after the type: std::string const&
  • For string IntToString(...) and ToString(const double &val...) why you don't use std::ostringstream?
  • In FormatNoVariadicTemplates.h why you don't use functions for FNVT_BEGIN and FNVT_END?

Have you any some benchmark results?

PS: good idea ;) I'm usually use to Qt functions, we are using this library in our projects or stl.

\$\endgroup\$
  • \$\begingroup\$ For point 1), I prefer to use names without the namespace, it really bothers me having to type it every time 2) I sometimes use it mostly for consistency; can't hurt 3) That's a personal preference 4) std::ostringstreams is extremely slow, and it's the main reason boost::format is over 10 times slower than printf(), even with optimizations on 5) It cannot be converted to a function because of the ## preprocessor trick (see ARGUMENT()) \$\endgroup\$ – Thomas Bonini May 1 '11 at 15:57
  • \$\begingroup\$ Few links about some points, it's not very important, but i use it. 1) gotw.ca/publications/c++cs.htm (59) 3) codeproject.com/KB/cpp/… And 5) It can be template functions, i think. \$\endgroup\$ – siquell May 1 '11 at 16:45
  • 1
    \$\begingroup\$ The thing about using in headers is important. \$\endgroup\$ – Lstor May 2 '11 at 12:24
5
\$\begingroup\$

I have a few comments, in no particular order.

It seems to me like you can't decide between std::string and const char*. Pick one.

(Maybe I'll get flamed for this one.) You say you care about performance, but your use of vector and string tell a different story. It seems to me like heap allocation is completely unneccessary for most of this, and for this reason I would avoid STL and C++ strings. Or, if not avoiding them entirely, I would avoid using so many std::strings as temporaries while formatting. The functions that do this should maybe take a destination buffer as a parameter.

It seems strange that your internal representation of options remains a string at all times. For example:

static const char *normalStrings[] = { "true", "false" };
static const char *alternativeStrings[] = { "yes", "no" };
const bool alternative = (options == "a");
const char **strings = alternative ? alternativeStrings : normalStrings;

This itself is a bit hard for an outsider to follow as written, but aside from that, do you really wantoptions == "a"? What if you want more than one option? Wouldn't a check for a be more like strchr(options, 'a')? In which case I would say do you really want O(n) lookup for options? Maybe this doesn't make sense as a string. Maybe you want to store them internally as flags in an integer. Even if you don't want to combine options it still might look less weird as an enum or constant. There are a lot of magic literals floating around in this code.

There's lots of code that's more verbose than it has to be, for example:

/* Find the '{' */
for (int i = start; i < end; ++i)
{
    if (str[i] != '{')
        continue;

    /* Check if there is enough space for the minimum spaceholder length, 3: {.} */
    if (end - i < 3)
        continue;

The for loop seems to be reinventing strchr. I would much prefer something like this:

char *current, *end;

while ((current=strchr(str, '{')) &&
       (end=strchr(current, '}')))
{
   //
   // TODO: do something with placeholder at (current, end)
   //

   str = end + 1;
}

Or perhaps more C-like (though I realize you're writing C++ it looks like you've already rejected some of its idioms) would be something like this:

while (*str)
{
   if (*str == '{' &&
       (end=strchr(str, '}')))
   {
      //
      // TODO: evaluate placeholder at (str, end) into dst
      //

      str = end+1;
   }
   else
   {
      // TODO: make sure this doesn't overflow :-)
      *dst++ = *str++;
   }
}

IntToString could have a lot of improvements... One trivial stylistic thing is that "0123456789" is a lot easier on the eyes than { '0', '1', '2', ... }. More importantly, do you have to do this with a separate heap allocation? Maybe it should be passed a destination buffer. The reserve method being called with a constant also looks a bit weird because you've just done the conversion inside tmp, so you know the exact length...

Lastly, as a matter of habit, if you're going to use sprintf -- well don't. Use snprintf instead. (If you happen to be using a version of Visual Studio that doesn't have that, there's also sprintf_s or StringCchPrintf.)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.