cpp-fstring is a C++ code processor that expands {var} type statements inside strings to equivalent fmt::format commands. So you can do things like:
enum class Color { red, yellow, green, blue };
enum class Fruit { orange, apple, banana };
std::map<Color, std::vector<Fruit>> mc = {
{Color::red, {Fruit::apple}},
{Color::yellow, {Fruit::apple, Fruit::banana}},
};
...
std::cout << "fruit by colors: {mc=} \n";
result:
fruit by colors: mc = {red: [apple], yellow: [apple, banana]}
cpp-fstring generates the boilerplate code to display enums and containers like structs and classes.
- This project has been set up using PyScaffold
- Useful post explaining C++ templates
- Discussions on stackoverflow like enum to string in modern C++
- C++ formatting library {fmt}
- Python interface to the Clang indexing library
- Packaged version of Clang Python Bindings libclang
Just got tired waiting for python style f-strings in C++ . Proposals like Interpolated literals seem to hit the "lack of reflection" brick wall. Hopefully by C++30 we could do something like the Scalable Reflection in C++ proposal:
#include <meta>
template<Enum T>
std::string to_string(T value) {
template for (constexpr auto e: std::meta::members_of(^T)) {
if ([:e:] == value) {
return std::string(std::meta::name_of(e));
}
}
return "<unnamed>";
}
In the mean time, cpp-fstring cheats by pre-processing C++17 code. So:
enum class Color { red, yellow, green = 20, blue };
std::cout << "the fruit is {Color::yellow}\n";
explodes into:
enum class Color { red, yellow, green = 20, blue };
std::cout << fmt::format("the fruit is {}\n", Color::yellow);
// Generated formatter for PUBLIC enum Color of type INT scoped
constexpr auto format_as(const Color obj) {
fmt::string_view name = "<missing>";
switch (obj) {
case Color::red : name = "red" ; break; // index=0
case Color::yellow: name = "yellow"; break; // index=1
case Color::green : name = "green" ; break; // index=20
case Color::blue : name = "blue" ; break; // index=21
}
return name;
}
while:
template <typename T, template<typename...> class C>
class Container {
public:
void addData(const T& data) {
container.push_back(data);
}
private:
C<T> container;
};
gets an extra to_string() function:
template <typename T, template<typename...> class C>
class Container {
public:
void addData(const T& data) {
container.push_back(data);
}
private:
C<T> container;
public:
// Generated to_string() for PUBLIC CLASS_TEMPLATE Container<T, C>
auto to_string() const {
return fstr::format("Container<T:={}>: C<T> container={}", fstr::get_type_name<T>(), container);
}
};
To install the tool, use:
pip install cpp-fstring
The following command then converts foo.cc into foo.cpp:
cpp-fstring foo.cc -I ../include > foo.cpp
You also need to add this include to foo.cc:
#include "fstr.h"
fstr.h contains helper routines needed to stringify enums and classes. An example of using cpp-fstring in cmake environment is at cpp-fstring-examples
At present only clang is supported--gcc and MinGW are in-progress.
There are 2 main dependencies: python libclang library to run cpp-format and C++ fmt library to display objects. In cmake environment the best way to pickup latest version of fmt library is:
CPMAddPackage(NAME fmt SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/fmt" GIT_REPOSITORY "https://github.com/fmtlib/fmt.git" GIT_TAG "master")
or:
CPMAddPackage("gh:fmtlib/fmt#10.0.0")
Generated code needs at least 10.0.0 version of fmt. cpp-fstring also needs libclang :
pip install libclang
libclang installs the dynamic library file (libclang.dylib, libclang.dll or libclang.so) in a path like /opt/homebrew/lib/python3.11/site-packages/clang/native/libclang.dylib . If an incorrect version of library file is installed, you can get a strange error like this You can download a more recent version of libclang library from:
https://github.com/llvm/llvm-project/releases/
The only file you need from binary distribution is the libclang dynamic lib for your machine, ie libclang.dylib, libclang.dll or libclang.so. You might have to copy this file to the native directory of python clang lib.
See demo_misc.cpp for a demo of Format Specifiers, Dates, Expressions and Ranges:
using IArr = std::valarray<int>;
IArr a {1,2,3};
IArr b {4,5,6};
IArr ab = std::pow(a, b);
IArr ba = std::pow(b, a);
IArr abba = ab+ba;
cout << R"(
Valarray:
a^b + b^a = {a}^{b} + {b}^{a}
= {ab} + {ba}
= {abba}
min({abba}) = {abba.min()}
sum({abba}) = {abba.sum()}
max({abba}) = {abba.max()}
)" ;
outputs:
Valarray:
a^b + b^a = [1, 2, 3]^[4, 5, 6] + [4, 5, 6]^[1, 2, 3]
= [1, 32, 729] + [4, 25, 216]
= [5, 57, 945]
min([5, 57, 945]) = 5
sum([5, 57, 945]) = 1007
max([5, 57, 945]) = 945
See enum_namespace.cpp for examples of simple enums:
namespace roman {
enum class sym {M, D, C, L, X, V, I};
std::map<sym, int> numerals = {
{sym::M, 1000},
{sym::D, 500},
{sym::C, 100},
{sym::L, 50},
{sym::X, 10},
{sym::V, 5},
{sym::I, 1}
};
} // namespace roman
...
std::cout << " {roman::numerals=}\n";
outputs:
roman::numerals={M: 1000, D: 500, C: 100, L: 50, X: 10, V: 5, I: 1}
See class_ctad.cpp for an example of derived template classes:
#include <iostream>
#include "fstr.h"
template<class T>
struct A {
T t;
struct {
long a, b;
} u;
};
template<class T>
struct B {
T t;
A<T> a;
};
int main() {
using std::cout;
auto b = B<int>{1, {2,{3,4}}};
cout << " {b=}";
}
outputs:
b=B<T:=int>: T t=1, A<T> a=A<T:=int>: T t=2, long u.a=3, u.b=4
4 underlying reasons behind stuff that doesn't work:
- Bugs in libclang, eg
- iterator class variables are incorrectly parsed. See this issue :
const std::vector<int>::const_iterator i_iter;
- base class with templates are sometimes missing in the derived class, so x doesn't show up when dumping Y() :
template <typename T> class X {
public:
T x;
};
class Y : public X<bool> {
int y = 13;
};
- missing vector variable in class, see issue :
struct Map {
std::map<int, std::vector<int>> m_is_invisible;
};
- Limitations in fmt:: library, eg wchar_t is not completely supported even with xchar.h:
#include <fmt/xchar.h>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <map>
int main() {
// works
std::map<int, char> box1 = { {1, L'⎧'}, {2, L'╭'} };
fmt::print("box1: {}\n", box1);
// doesn't work..needs additional formatter to be defined to handle wchar_t
std::map<int, wchar_t> box2 = { {1, L'⎧'}, {2, L'╭'} };
fmt::print("box2: {}\n", box2);
return 0;
}
- C++ features, eg inside functions we can't have other functions or template struct so there is no way to define a formatter for enum line in :
int main() {
//can't print enum becaause it's inside main()
enum class line { words, spaces };
}
- Bugs/limitations of cpp-fstring.
- majority of bugs are of course in this section, eg code with:
- ambiguous partial specializations
- template parameter packs
- ...
Perfect segway to contributing.
This project uses pre-commit <https://pre-commit.com/> :
cd cpp_fstring
pip install pre-commit
pre-commit install
pre-commit autoupdate
Sandeep - @d-e-e-p <https://github.com/d-e-e-p>
## License
The project is available under the MIT <https://opensource.org/licenses/MIT> license. See LICENSE file for details