I'm using C++17 with templates and recursion to replace the C Va_Args.
Currently only floats are supported, more types are following once float is working ;)
class CWrite
{
public:
template<typename NextT, typename ...RestT>
static std::string Format(NextT next, RestT ... rest);
private:
template<typename T>
static constexpr bool is_float = std::is_same_v<T, float>;
template<typename T>
static constexpr bool IsValidParam();
template<typename LastT>
static std::string Format(LastT last);
///Empty param case
static std::string Format();
};
// +++++++++++++++++++ Implementation ++++++++++++++++++++++++++
template<typename T>
constexpr bool CWrite::IsValidParam()
{
bool bRes = false;
bRes |= is_float<T>;
return bRes;
}
template<typename NextT, typename ...RestT>
std::string CWrite::Format(NextT next, RestT ... rest)
{
std::string strRes = Format(next);
strRes += Format(rest...);
return strRes;
}
template<typename LastT>
std::string CWrite::Format(LastT last)
{
std::string strRes;
if (is_float<LastT>)
{
strRes = "float:";
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
strRes += buffer;
}
return strRes;
}
///Empty param case
std::string CWrite::Format()
{
return "";
}
calling this with
std::string strRes = CWrite::Format(1.0f, 2.0f, 3.0f, 4.0f, 5);
results in a warning for snprintf()
format '%f' expects argument of type 'double', but argument 4 has type 'int'
I'd expect that IsValidParam returns false for the last argument which should be an int.
https://onlinegdb.com/B1A72GHgU
Could you help me out here?
Did i miss something here?
If you can use C++17, you should use if constexpr in the following function
template<typename LastT>
std::string CWrite::Format(LastT last)
{
std::string strRes;
// VVVVVVVVV <-- add "constexpr" here
if constexpr (is_float<LastT>)
{
strRes = "float:";
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
strRes += buffer;
}
return strRes;
}
The problem is that, using a simple if instead if constexpr, the compiler has to compile the statement (the part inside the { ... }) also when is_float<LastT> is false.
If you can't use C++17... I suppose you can differentiate the function through overloading
std::string CWrite::Format (float last)
{
std::string strRes { "float:" };
char buffer[10] = { };
snprintf(buffer, 10, "%f", last);
return strRes += buffer;
}
std::string CWrite::Format (int last)
{
std::string strRes { "int:" };
char buffer[10] = { };
snprintf(buffer, 10, "%i", last);
return strRes += buffer;
}
max66's answer addresses the reason why your method has a problem with the format string and how to fix it. Basically you just need some way of picking a different format string based on the type of the value being formatted.
However, I'd like to point out another flaw: you assume that any given value will only require 9 characters to convert into a string. For very large values (e.g. 1e22) this will fail. GCC will actually issue you a warning if it can determine this at compile time.
Additionally, your current implementation allocates many strings and recursively appends them together. This is - of course - highly inefficient and diminishes the speed of the printf family of functions to the point that it's not really worth using them.
Also your solution doesn't check for format errors (snprintf() returns negative in this case). And in such cases you may be appended undefined memory onto your string, as I'm not certain the C standard guarantees to null terminate the buffer on failure cases (but it might).
My solution is to have a function that formats a given argument in-place onto the end of a std::string. Additionally it handles format errors and cases where 9 bytes is insufficient to hold the formatted value.
Additionally I impose SFINAE restrictions on the argument types to ensure it can only be called with types that we support.
Here's my solution with comments to explain what does what and why:
#include <string>
#include <type_traits>
#include <iostream>
// checks if T is a type we support
template<typename T>
inline constexpr bool allowed_type = std::is_floating_point_v<T> || std::is_integral_v<T>;
// the initial amount of space for stringifying each argument
constexpr std::size_t APPEND_PADDING = 20;
// returns the appropriate format string for type T (T assumed to be supported)
template<typename T>
const char *fmt_string()
{
if constexpr (std::is_floating_point_v<T>) return "%f";
else return "%d";
}
// stringifys val onto the end of str (T assumed to be supported)
template<typename T>
void append(std::string &str, T val)
{
std::size_t prev_size = str.size(); // remember the previous size of str
str.resize(prev_size + APPEND_PADDING); // allocate the space we need
const char *fmt = fmt_string<T>(); // get the format string to use
// format the value and check the save the return value
int res = snprintf(&str[prev_size], APPEND_PADDING, fmt, val);
// on format error, just skip it (or )
if (res < 0) str.resize(prev_size);
// if we didn't have enough room we need to try again with the correct size
if ((std::size_t)res >= APPEND_PADDING)
{
str.resize(prev_size + res + 1); // make space for the characters we need and the null terminator
snprintf(&str[prev_size], res + 1, fmt, val); // format the string again (this time will work)
str.pop_back(); // remove the null terminator
}
// otherwise we had enough room, so just truncate to the written characters
else str.resize(prev_size + res);
}
// formats all of args into a single string (only allows supported types)
template<typename ...Args, std::enable_if_t<(allowed_type<Args> && ...), int> = 0>
std::string format(Args ...args)
{
std::string str; // create an empty buffer string to store the result
str.reserve(sizeof...(args) * APPEND_PADDING); // predict how much space we'll need for everything
int _[] = { (append(str, args), 0)... }; // append all the args to str one at a time
(void)_; // suppress unused variable warnings (will just be optimized away)
return str;
}
int main()
{
std::cout << format(1, 2, 2.3, 3, 4.4, 5, 1e22) << '\n';
}
Note that this runs all the formatted strings together with no separation. Fixing this would be as simple as changing the format strings returned from fmt_string().
I used different function names than you did, but you get the idea. format() is the function you would use.
Related
Context:
In my company we generate a lot of types based on IDL files. Some of the types require special logic so they are handcoded but follow the same pattern as the generated ones. We have a function which all types must implement which is a name function. This will return the type name as a char* string and the function is constexpr.
Problem:
The problem is regarding collections which could contain other collections nested potentially N number of times. I therefore am trying to concatenate two or more char* strings at compile time.
Pseudocode of what I want to achieve:
template <typename T>
constexpr char* name()
{
constexpr char* collectionName = "std::vector";
constexpr char* containedTypeName = name<T>();
return concat(collectionName, "<", containedTypeName, ">");
}
Note:
There are examples out there which does something like this but is done with char[] or the use of static variables.
The question:
How can I make a constexpr function which return a char* which consists of two or more concatenated char* strings at compile time? I am bound to C++17.
From constexpr you cannot return char* which is constructed there... You must return some compile time known(also its size) constant thingy.
A possible solution could be something like:
#include <cstring>
// Buffer to hold the result
struct NameBuffer
{
// Hardcoded 128 bytes!!!!! Carefully choose the size!
char data[128];
};
// Copy src to dest, and return the number of copied characters
// You have to implement it since std::strcpy is not constexpr, no big deal.
constexpr int constexpr_strcpy(char* dest, const char* src);
//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr NameBuffer name()
{
// We will return this
NameBuffer buf{};
constexpr const char* collectionName = "std::vector";
constexpr const char* containedTypeName = "dummy";
// Copy them one by one after each other
int n = constexpr_strcpy(buf.data, collectionName);
n += constexpr_strcpy(buf.data + n, "<");
n += constexpr_strcpy(buf.data + n, containedTypeName);
n += constexpr_strcpy(buf.data + n, ">");
// Null terminate the buffer, or you can store the size there or whatever you want
buf.data[n] = '\0';
return buf;
}
Demo
And since the returned char* is only depends on the template parameter in your case, you can create templated variables, and create a char* to them, and it can act like any other char*...
EDIT:
I have just realized that your pseudo code will never work!! Inside name<T>() you are trying to call name<T>().
You must redesign this!!! But! With some hack you can determine the size at compile time somehow for example like this:
#include <cstring>
#include <iostream>
template<std::size_t S>
struct NameBuffer
{
char data[S];
};
// Copy src to dest, and return the number of copied characters
constexpr int constexpr_strcpy(char* dest, const char* src)
{
int n = 0;
while((*(dest++) = *(src++))){ n++; }
return n;
}
// Returns the len of str without the null term
constexpr int constexpr_strlen(const char* str)
{
int n = 0;
while(*str) { str++; n++; }
return n;
}
// This template parameter does nothing now...
// I left it there so you can see how to create the template variable stuff...
//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr auto createName()
{
constexpr const char* collectionName = "std::vector";
constexpr const char* containedTypeName = "dummy";
constexpr std::size_t buff_size = constexpr_strlen(collectionName) +
constexpr_strlen(containedTypeName) +
2; // +1 for <, +1 for >
/// +1 for the nullterm
NameBuffer<buff_size + 1> buf{};
/// I'm lazy to rewrite, but now we already calculated the lengths...
int n = constexpr_strcpy(buf.data, collectionName);
n += constexpr_strcpy(buf.data + n, "<");
n += constexpr_strcpy(buf.data + n, containedTypeName);
n += constexpr_strcpy(buf.data + n, ">");
buf.data[n] = '\0';
return buf;
}
// Create the buffer for T
template<typename T>
static constexpr auto name_buff_ = createName<T>();
// point to the buffer of type T. It can be a function too as you wish
template<typename T>
static constexpr const char* name = name_buff_<T>.data;
int main()
{
// int is redundant now, but this is how you could use this
std::cout << name<int> << '\n';
return 0;
}
Demo
I have a vector of 2N lines where the second half (N lines) is basically the same as the first half but with a single character changed, e.g.:
std::vector<std::string> tests{
// First half of lines with '=' as separator between key and value
"key=value",
...
// Second half of lines with ' ' as separator between key and value
"key value",
...
};
Is there a way to parameterize the separator (i.e., = or ) to avoid repeating the lines during the initialization, using the uniform initialization construct? I wonder if there's a better way than creating it with a for loop.
By reading from documentation, it seems not to be possible.
Thanks
Since this is tagged c++17 and you said the strings are known at compile-time, it is technically possible to perform uniform initialization by leveraging a variadic function-template and unpacking the parameters twice, and this would produce the modified string at compile-time.
The idea, in the simplest form, is to do this:
template <typename...Strings>
auto make_string_vector(const Strings&...strings) -> std::vector<std::string>
{
// Unpacks the strings twice; first unmodified, second with '=' swapped with ' '
return std::vector<std::string>{{
strings..., to_space_string(strings)...
}};
};
Where to_space_string is a class that returns a string-like object, done at compile-time.
To do this, we need to make a simple holder that acts like a string is convertible to std::string_view at compile time. This is to ensure that the string we modify has its own separate lifetime and does not dangle:
// A holder for the data so that we can convert it to a 'std::string' type
template <std::size_t N>
struct static_string {
char data[N];
constexpr operator std::string_view() const noexcept {
return std::string_view{data, N};
}
};
Then all we need is the function that takes a compile-time string (array of chars), copies it into the static_string<N> object, and returns it:
// std::string_view used so that we can do this constexpr
template <std::size_t N>
constexpr auto to_space_string(const char(&string)[N]) -> static_string<N>
{
auto storage = static_string<N>{};
std::transform(&string[0], &string[N], &storage.data[0], [](char c){
if (c == '=') { return ' '; }
return c;
});
return storage;
}
The last needed tweak would be for the initializer list to be a sequence of std::string objects, which we can do with static_casts:
template <typename...Strings>
auto make_string_vector(const Strings&...strings) -> std::vector<std::string>
{
return std::vector<std::string>{{
static_cast<std::string>(strings)...,
static_cast<std::string>(to_space_string(strings))...
}};
};
With this, code like:
auto vec = make_string_view("hello=world", "goodbye=world");
will produce a vector containing
hello=world
goodbye=world
hello world
goodbye world
Live Example
Note:
If we didn't use a static_string or some equivalent and instead used string_view directly, the string would dangle. For example:
template <std::size_t N>
constexpr auto to_space_string(const char(&string)[N]) -> std::string_view
{
char storage[N];
std::transform(&string[0], &string[N], &storage[0], [](char c){
if (c == '=') { return ' '; }
return c;
});
// dangles after being returned!
return std::string_view{&storage[0], N};
}
In the above case, we return a reference to temporary storage storage[N], thus causing a dangling pointer (UB). The static_string creates an object first whose lifetime is passed into the caller (make_string_vector) and then gets converted to a std::string.
If only one = per line is allowed then following can work
std::vector<std::string> init_from_key_eq_val(std::initializer_list<const char *> strings)
{
std::vector<std::string> result;
result.reserve(2 * strings.size());
copy(strings.begin(), strings.end(), back_inserter(result));
transform(strings.begin(), strings.end(), back_inserter(result),
[](const char* str)
{
std::string str_copy(str);
size_t eq_pos = str_copy.find('=');
str_copy[eq_pos] = ' ';
return str_copy;
});
return result;
}
int main()
{
std::vector<std::string> vec = init_from_key_eq_val({"key=value"});
}
From the performance point of view, it is no better than doing it with a loop though
Is there a way to define as static constexpr string literal member in Qt? I.e. something like the following:
class X
{
static constexpr QString tag = "mytag";
};
I did what Jesper recommended in his comment, I used QLatin1String. But I used it through a small helper class to avoid the strlen() call in QLatin1String:
struct ConstLatin1String : public QLatin1String
{
constexpr ConstLatin1String(const char* const s) :
QLatin1String(s, static_cast<int>(std::char_traits<char>::length(s))) {}
};
This allows to do:
static constexpr ConstLatin1String mystring = "foo";
Inspired by Silicomancer answer but works with c++11
#include <QLatin1String>
namespace detail {
// Helper function for getting compile-time string literal's length
// Given from: https://stackoverflow.com/q/27498592/
// c++11 analogue of: std::char_traits<char>::length(str); from c++14
constexpr std::size_t constLength(const char* str)
{
return ((*str) == '\0') ? 0 : constLength(str + 1) + 1;
}
} // namespace detail
// Constructs compile-time <QLatin1String> from string literal
constexpr QLatin1String make_string(const char* str)
{
return QLatin1String(str, detail::constLength(str));
}
// Alternate implementation for string literals only, without
// recursive function call (reduces compilation time), using
// known in compile-time size.
template <std::size_t SIZE>
constexpr QLatin1String make_string_from_literal(const char (&str)[SIZE])
{
return QLatin1String(str, SIZE);
}
// Example of usage:
static constexpr QLatin1String STR = make_string("hello_world");
Testing
// Helper function for compile-time strings comparison
// Given from https://stackoverflow.com/a/27490954/
constexpr bool strings_equal(const char* a, const char* b) {
return (*a == *b) && (*a == '\0' || strings_equal(a + 1, b + 1));
}
// Compile-time in-place construction check
static_assert( strings_equal(make_string("foo").data(), "foo"), "test failed");
// Compile-time constant construction check
static constexpr QLatin1String STR = make_string("bar");
static_assert( strings_equal(STR.data(), "bar"), "test failed");
Addition for inspiration
It's perfectly works with compile-time hashing, like in that brilliant answer, which based on (that and that)
// Note: `len` must be without null-character sign
constexpr uint32_t crc32(const char* str, std::size_t len) {
return detail::crc32(len - 1, str) ^ 0xFFFFFFFF;
}
template <std::size_t LEN>
constexpr std::uint32_t crc32(const char (&str)[LEN]) {
return crc32(str, LEN - 1);
}
// Overload for <QLatin1String>
// Notice that, next methods: QLatin1String::{.data(), .size()}
// marked as constexpr only since Qt 5.6.
constexpr std::uint32_t crc32(const QLatin1String& str) {
return crc32(str.data(), str.size());
}
// ------------------------------------------------------------
// Test with explicitely specified literal size
static_assert( crc32("hello world", 11) == 0xd4a1185, "CRC32 test failed");
static_assert( crc32("foo bar", 7) == 0xbe460134, "CRC32 test failed");
// Test with implicitly calculated literal size
static_assert( crc32("hello world") == 0xd4a1185, "CRC32 test failed");
static_assert( crc32("foo bar") == 0xbe460134, "CRC32 test failed");
// Test with convenient overload for <QLatin1String>
static constexpr QLatin1String STR_1 = make_string("hello world");
static constexpr QLatin1String STR_2 = make_string("foo bar");
static_assert( crc32(STR_1) == 0xd4a1185, "CRC32 test failed");
static_assert( crc32(STR_2) == 0xbe460134, "CRC32 test failed");
That allow compile-time string matching via switch-case.
Both switch cases are equal and computed in compile-time :)
string constructed in compile-time, which allows to use it in any cases, and compute hash in place, where it needed.
pre-computed hash from string. string not exists.
inplace usage without any ceremony :D
static constexpr QLatin1String MATCH_1 = make_string("value_1"); // 1)
static constexpr auto MATCH_2 = crc32( make_string("value_2") ); // 2)
const QLatin1String str = /* ... */;
switch( crc32(str) )
{
case crc32(MATCH_1): { do_something_for_value_1(); } break; // 1)
case MATCH_2: { do_something_for_value_2(); } break; // 2)
case crc32("value_3"): { do_something_for_value_3(); } break; // 3)
}
Considering that QString is a (potentially) heap-allocated string, and you can't allocate memory in constexpr code, no. It's no more valid at compile-time than using std::string.
constexpr QStringView tag = u"mytag";
I want to create a string with embedded information. One way (not the only way) of achieving what I want is called string interpolation or variable substitution, wherein placeholders in a string are replaced with actual values.
In C, I would do something like this:
printf("error! value was %d but I expected %d",actualValue,expectedValue)
whereas if I were programming in python, I would do something like this:
"error! value was {0} but I expected {1}".format(actualValue,expectedValue)
both of these are examples of string interpolation.
How can I do this in C++?
Important Caveats:
I know that I can use std::cout if I want to print such a message to standard output (not string interpolation, but prints out the kind of string I want):
cout << "error! value was " << actualValue << " but I expected "
<< expectedValue;
I don't want to print a string to stdout. I want to pass a std::string as an argument to a function (e.g. the constructor of an exception object).
I am using C++11, but portability is potentially an issue, so knowing which methods work and don't work in which versions of C++ would be a plus.
Edit
For my immediate usage, I'm not concerned about performance (I'm raising an exception for cryin' out loud!). However, knowing the relative performance of the various methods would be very very useful in general.
Why not just use printf itself (C++ is a superset of C after all...)? This answer discusses some reasons why not. As far as I can understand, type safety is a big reason: if you put %d, the variable you put in there had better really be convertible to an integer, as that's how the function figures out what type it is. It would be much safer to have a method which uses compile-time knowledge of the actual type of the variables to be inserted.
In C++20 you will be able to use std::format.
This will support python style formatting:
string s = std::format("{1} to {0}", "a", "b");
There is already an implementation available: https://github.com/fmtlib/fmt.
Method 1: Using a string stream
It looks like std::stringstream gives a quick solution:
std::stringstream ss;
ss << "error! value was " << actualValue << " but I expected " << expectedValue << endl;
//example usage
throw MyException(ss.str())
Positive
no external dependencies
I believe this works in C++ 03 as well as c++ 11.
Negative
reportedly quite slow
a bit more messy: you must create a stream, write to it, and then get the string out of it.
Method 2: Boost Format
The Boost Format library is also a possibility. Using this, you would do:
throw MyException(boost::format("error! value was %1% but I expected %2%") % actualValue % expectedValue);
Positive
pretty clean compared to stringstream method: one compact construct
Negative
reportedly quite slow: uses the stream method internally
it's an external dependency
Edit:
Method 3: variadic template parameters
It seems that a type-safe version of printf can be created by using variadic template parameters (the techincal term for a template that takes an indefinite number of template parameters). I have seen a number of possibilities in this vein:
This question gives a compact example and discusses performance problems with that example.
This answer to that question, whose implementation is also quite compact, but reportedly still suffers from performance issues.
The fmt library, discussed in this answer, is reportedly quite fast and seems to be as clean as printf itself, but is an external dependency
Positive
usage is clean: just call a printf-like function
The fmt library is reportedly quite fast
The other options seem quite compact (no external dependency required)
Negative
the fmt library, while fast, is an external dependency
the other options apparently have some performance issues
In C++11 you can use std::to_string:
"error! value was " + std::to_string(actualValue) + " but I expected " + std::to_string(expectedValue)
It's not pretty, but it's straightforward, and you can use a macro to shrink it a bit. Performance is not great, since you do not reserve() space beforehand. Variadic templates would probably be faster and look nicer.
This kind of string construction (instead of interpolation) is also bad for localization, but you'd probably use a library if you needed that.
Use whatever you like:
1) std::stringstream
#include <sstream>
std::stringstream ss;
ss << "Hello world!" << std::endl;
throw std::runtime_error(ss.str());
2) libfmt : https://github.com/fmtlib/fmt
#include <stdexcept>
throw std::runtime_error(
fmt::format("Error has been detected with code {} while {}",
0x42, "copying"));
C++17 solution that works both for std::string & for std::wstring (Tested on VS2019 & VS2022):
#include <string>
#include <stdexcept>
#include <cwchar>
#include <cstdio>
#include <type_traits>
template<typename T, typename ... Args>
std::basic_string<T> string_format(T const* const format, Args ... args)
{
int size_signed{ 0 };
// 1) Determine size with error handling:
if constexpr (std::is_same_v<T, char>) { // C++17
size_signed = std::snprintf(nullptr, 0, format, args ...);
}
else {
size_signed = std::swprintf(nullptr, 0, format, args ...);
}
if (size_signed <= 0) {
throw std::runtime_error("error during formatting.");
}
const auto size = static_cast<size_t>(size_signed);
// 2) Prepare formatted string:
std::basic_string<T> formatted(size, T{});
if constexpr (std::is_same_v<T, char>) { // C++17
std::snprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
}
else {
std::swprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
}
return formatted; // Named Return Value Optimization (NRVO), avoids an unnecessary copy.
}
// USE EXAMPLE: //
int main()
{
int i{ 0 };
const std::string example1 = string_format("string. number %d.", ++i); // => "string. number 1."
const std::wstring example2 = string_format(L"wstring. number %d.", ++i); // => L"wstring. number 2."
}
DISCLAIMER:
The subsequent code is based on an article I read 2 years ago. I will find the source and put it here ASAP.
This is what I use in my C++17 project. Should work with any C++ compiler supporting variadic templates though.
Usage:
std::string const word = "Beautiful";
std::string const message = CString::format("%0 is a %1 word with %2 characters.\n%0 %2 %0 %1 %2", word, "beautiful", word.size());
// Prints:
// Beautiful is a beautiful word with 9 characters.
// Beautiful 9 Beautiful beautiful 9.
The class implementation:
/**
* The CString class provides helpers to convert 8 and 16-bit
* strings to each other or format a string with a variadic number
* of arguments.
*/
class CString
{
public:
/**
* Format a string based on 'aFormat' with a variadic number of arbitrarily typed arguments.
*
* #param aFormat
* #param aArguments
* #return
*/
template <typename... TArgs>
static std::string format(
std::string const&aFormat,
TArgs &&...aArguments);
/**
* Accept an arbitrarily typed argument and convert it to it's proper
* string representation.
*
* #tparam TArg
* #tparam TEnable
* #param aArg
* #return
*/
template <
typename TArg,
typename TEnable = void
>
static std::string toString(TArg const &aArg);
/**
* Accept a float argument and convert it to it's proper string representation.
*
* #tparam TArg
* #param arg
* #return
*/
template <
typename TArg,
typename std::enable_if<std::is_floating_point<TArg>::value, TArg>::type
>
static std::string toString(const float& arg);
/**
* Convert a string into an arbitrarily typed representation.
*
* #param aString
* #return
*/
template <
typename TData,
typename TEnable = void
>
static TData const fromString(std::string const &aString);
template <
typename TData,
typename std::enable_if
<
std::is_integral<TData>::value || std::is_floating_point<TData>::value,
TData
>::type
>
static TData fromString(std::string const &aString);
private:
/**
* Format a list of arguments. In this case zero arguments as the abort-condition
* of the recursive expansion of the parameter pack.
*
* #param aArguments
*/
template <std::size_t NArgs>
static void formatArguments(std::array<std::string, NArgs> const &aArguments);
/**
* Format a list of arguments of arbitrary type and expand recursively.
*
* #param outFormatted
* #param inArg
* #param inArgs
*/
template <
std::size_t NArgs,
typename TArg,
typename... TArgs
>
static void formatArguments(
std::array<std::string, NArgs> &aOutFormatted,
TArg &&aInArg,
TArgs &&...aInArgs);
};
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <typename... TArgs>
std::string CString::format(
const std::string &aFormat,
TArgs &&...aArgs)
{
std::array<std::string, sizeof...(aArgs)> formattedArguments{};
formatArguments(formattedArguments, std::forward<TArgs>(aArgs)...);
if constexpr (sizeof...(aArgs) == 0)
{
return aFormat;
}
else {
uint32_t number = 0;
bool readNumber = false;
std::ostringstream stream;
for(std::size_t k = 0; k < aFormat.size(); ++k)
{
switch(aFormat[k])
{
case '%':
readNumber = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// Desired behaviour to enable reading numbers in text w/o preceding %
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
if(readNumber)
{
number *= 10;
number += static_cast<uint32_t>(aFormat[k] - '0');
break;
}
default:
if(readNumber)
{
stream << formattedArguments[std::size_t(number)];
readNumber = false;
number = 0;
}
stream << aFormat[k];
break;
#pragma GCC diagnostic warning "-Wimplicit-fallthrough"
}
}
if(readNumber)
{
stream << formattedArguments[std::size_t(number)];
readNumber = false;
number = 0;
}
return stream.str();
}
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <typename TArg, typename enable>
std::string CString::toString(TArg const &aArg)
{
std::ostringstream stream;
stream << aArg;
return stream.str();
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <
typename TArg,
typename std::enable_if<std::is_floating_point<TArg>::value, TArg>::type
>
std::string CString::toString(const float& arg)
{
std::ostringstream stream;
stream << std::setprecision(12) << arg;
return stream.str();
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <std::size_t argCount>
void CString::formatArguments(std::array<std::string, argCount> const&aArgs)
{
// Unused: aArgs
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <std::size_t argCount, typename TArg, typename... TArgs>
void CString::formatArguments(
std::array<std::string, argCount> &outFormatted,
TArg &&inArg,
TArgs &&...inArgs)
{
// Executed for each, recursively until there's no param left.
uint32_t const index = (argCount - 1 - sizeof...(TArgs));
outFormatted[index] = toString(inArg);
formatArguments(outFormatted, std::forward<TArgs>(inArgs)...);
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <
typename TData,
typename std::enable_if
<
std::is_integral<TData>::value || std::is_floating_point<TData>::value,
TData
>::type
>
TData CString::fromString(std::string const &aString)
{
TData const result{};
std::stringstream ss(aString);
ss >> result;
return result;
}
//<-----------------------------------------------------------------------------
If you don't mind using a preprocessor script, here is a more easy but handy solution: https://github.com/crazybie/cpp_str_interpolation. Then you can write the code like this:
string s1 = "world", s2 = "!";
cout << _F("hello, {s1+s2}") << endl;
it also support using like a template engine:
int a = 1;
float b = 2.3f;
cout << _F(R"(
`for (int i=0; i<2; i++) {`
a is {a}, i is {i}.
a+i is {a+i}.
`}`
b is {b}.
cout << "123" << endl;`
)") << endl;
I've grown very fond of this solution, std::format notwithstanding. I dislike it on several counts (use of macros, and the whole concept of operator << overloading). But the ease of use truly makes up for it.
#ifndef SS_HPP
#define SS_HPP
#include <sstream>
#include <iostream>
// usage: SS("xyz" << 123 << 45.6) returning a std::string rvalue.
#define SS(x) ( ((std::stringstream&)(std::stringstream() << x )).str())
#endif
Anything like Boost.Format in the C++11 standard? I've been able to avoid using Boost with a better C++11 option for every other need I've had.
For that matter, Boost.Format doesn't hold a candle to the syntax of Python format(). Something like that would be even better.
C++11, 14 and 17 don't provide anything like that.
However, C++20 provides std::format which is similar in spirit to Boost Format but with the design permitting more efficient implementation. The {fmt} library is an implementation of this formatting facility and it only requires C++11:
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
Disclaimer: I'm the author of {fmt} and C++20 std::format
There is a proposal for something similar to boost-format. However, it's neither part of C++11 nor C++14, nor has anything related to string formatting be added.
Here you can find the latest proposal. In contrast to boost-format, it's based on variadic templates.
http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3716.html
Python-like format string function implementation with c++11 regex and variadic templates.
/**
Helper code to unpack variadic arguments
*/
namespace internal
{
template<typename T>
void unpack(std::vector<std::string> &vbuf, T t)
{
std::stringstream buf;
buf << t;
vbuf.push_back(buf.str());
}
template<typename T, typename ...Args>
void unpack(std::vector<std::string> &vbuf, T t, Args &&... args)
{
std::stringstream buf;
buf << t;
vbuf.push_back(buf.str());
unpack(vbuf, std::forward<Args>(args)...);
}
}
/**
Python-like string formatting
*/
template<typename ... Args>
std::string format(const std::string& fmt, Args ... args)
{
std::vector<std::string> vbuf; // store arguments as strings
std::string in(fmt), out; // unformatted and formatted strings
std::regex re_arg("\\{\\b\\d+\\b\\}"); // search for {0}, {1}, ...
std::regex re_idx("\\b\\d+\\b"); // search for 0, 1, ...
std::smatch m_arg, m_idx; // store matches
size_t idx = 0; // index of argument inside {...}
// Unpack arguments and store them in vbuf
internal::unpack(vbuf, std::forward<Args>(args)...);
// Replace all {x} with vbuf[x]
while (std::regex_search(in, m_arg, re_arg)) {
out += m_arg.prefix();
auto text = m_arg[0].str();
if (std::regex_search(text, m_idx, re_idx)) {
idx = std::stoi(m_idx[0].str());
}
if(idx < vbuf.size()) {
out += std::regex_replace(m_arg[0].str(), re_arg, vbuf[idx]);
}
in = m_arg.suffix();
}
out += in;
return out;
}
Example: cpp.sh/9cvtz
using sprintf
For C++20, use std::format
there is the fmt-library from version C++11 on
some simple solution for formated output is printf
to use printf syntax to write a std::string, use the following snippet
minimal reproducible example: format std::string with printf syntax
interactive version
#include <iostream>
#include <string>
#include <stdio.h>
#include <assert.h>
template<typename... Args>
std::string fmt_str(std::string fmt, Args... args)
{
size_t bufferSize = 1000;
char *buffer = new char[bufferSize];
int n = sprintf(buffer, fmt.c_str(), args...);
assert (n >= 0 and n < (int) bufferSize - 1 && "check fmt_str output");
std::string fmtStr (buffer);
delete buffer;
return fmtStr;
}
int main()
{
int a=1, b=2;
double c=3.;
std::cout << fmt_str("%d plus %d is %f", a, b, c) << std::endl;
return 0;
}
output
1 plus 2 is 3.000000
template<typename... Args>
std::string fmt_str(const std::string& fmt, Args... args)
{
static const int bufferSize = 1000;
char buffer[bufferSize];
int n = snprintf(buffer, bufferSize, fmt.c_str(), args...);
assert(n >= 0 and n <= bufferSize - 1 && "check fmt_str output");
return (buffer);
}
//Based on Markus, a little improvements: input change to ref, buffer avoid new, use snprintf to avoid buffer overflow, return directly to avoid copy constructor. Using it in my project
//It should be a comment to Markus Dutschke's, not an answer, but the comment field can't format a code piece well, and I extract the code here.