Skip to content

Commit 2949e0e

Browse files
authored
Move formatting code to a different header file to improve compile times (#111)
Signed-off-by: John Sallay <[email protected]>
1 parent 6b8a886 commit 2949e0e

11 files changed

+131
-125
lines changed

bench/bm_pmt_dict_pack_unpack.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "CLI/Formatter.hpp"
88

99
#include <pmtv/pmt.hpp>
10+
#include <pmtv/format.hpp>
1011

1112
using namespace pmtv;
1213

bench/bm_pmt_dict_ref.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "CLI/Formatter.hpp"
88

99
#include <pmtv/pmt.hpp>
10+
#include <pmtv/format.hpp>
1011

1112
using namespace pmtv;
1213

include/pmtv/format.hpp

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Support for std::format is really spotty.
2+
// Gcc12 does not support it.
3+
// Eventually replace with std::format when that is widely available.
4+
#include <fmt/format.h>
5+
6+
namespace fmt {
7+
template <>
8+
struct formatter<pmtv::map_t::value_type> {
9+
template <typename ParseContext>
10+
constexpr auto parse(ParseContext& ctx) {
11+
return ctx.begin();
12+
}
13+
14+
template <typename FormatContext>
15+
auto format(const pmtv::map_t::value_type& kv, FormatContext& ctx) const {
16+
return fmt::format_to(ctx.out(), "{}: {}", kv.first, kv.second);
17+
}
18+
};
19+
20+
template <pmtv::Complex C>
21+
struct formatter<C> {
22+
template <typename ParseContext>
23+
constexpr auto parse(ParseContext& ctx) {
24+
return ctx.begin();
25+
}
26+
27+
template <typename FormatContext>
28+
auto format(const C& arg, FormatContext& ctx) const {
29+
if (arg.imag() >= 0)
30+
return fmt::format_to(ctx.out(), "{0}+j{1}", arg.real(), arg.imag());
31+
else
32+
return fmt::format_to(ctx.out(), "{0}-j{1}", arg.real(), -arg.imag());
33+
}
34+
};
35+
36+
37+
template<pmtv::IsPmt P>
38+
struct formatter<P>
39+
{
40+
41+
template <typename ParseContext>
42+
constexpr auto parse(ParseContext& ctx) {
43+
return ctx.begin();
44+
}
45+
46+
template <typename FormatContext>
47+
auto format(const P& value, FormatContext& ctx) const {
48+
// Due to an issue with the c++ spec that has since been resolved, we have to do something
49+
// funky here. See
50+
// https://stackoverflow.com/questions/37526366/nested-constexpr-function-calls-before-definition-in-a-constant-expression-con
51+
// This problem only appears to occur in gcc 11 in certain optimization modes. The problem
52+
// occurs when we want to format a vector<pmt>. Ideally, we can write something like:
53+
// return fmt::format_to(ctx.out(), "[{}]", fmt::join(arg, ", "));
54+
// It looks like the issue effects clang 14/15 as well.
55+
// However, due to the above issue, it fails to compile. So we have to do the equivalent
56+
// ourselves. We can't recursively call the formatter, but we can recursively call a lambda
57+
// function that does the formatting.
58+
// It gets more complicated, because we need to pass the function into the lambda. We can't
59+
// pass in the lamdba as it is defined, so we create a nested lambda. Which accepts a function
60+
// as a argument.
61+
// Because we are calling std::visit, we can't pass non-variant arguments to the visitor, so we
62+
// have to create a new nested lambda every time we format a vector to ensure that it works.
63+
using namespace pmtv;
64+
using ret_type = decltype(fmt::format_to(ctx.out(), ""));
65+
auto format_func = [&ctx](const auto format_arg) {
66+
auto function_main = [&ctx](const auto arg, auto function) -> ret_type {
67+
using namespace pmtv;
68+
using T = std::decay_t<decltype(arg)>;
69+
if constexpr (Scalar<T> || Complex<T>)
70+
return fmt::format_to(ctx.out(), "{}", arg);
71+
else if constexpr (std::same_as<T, std::string>)
72+
return fmt::format_to(ctx.out(), "{}", arg);
73+
else if constexpr (UniformVector<T> || UniformStringVector<T>)
74+
return fmt::format_to(ctx.out(), "[{}]", fmt::join(arg, ", "));
75+
else if constexpr (std::same_as<T, std::vector<pmt>>) {
76+
fmt::format_to(ctx.out(), "[");
77+
auto new_func = [&function](const auto new_arg) -> ret_type { return function(new_arg, function); };
78+
for (auto& a: std::span(arg).first(arg.size()-1)) {
79+
std::visit(new_func, a);
80+
fmt::format_to(ctx.out(), ", ");
81+
}
82+
std::visit(new_func, arg[arg.size()-1]);
83+
return fmt::format_to(ctx.out(), "]");
84+
// When we drop support for gcc11/clang15, get rid of the nested lambda and replace
85+
// the above with this line.
86+
//return fmt::format_to(ctx.out(), "[{}]", fmt::join(arg, ", "));
87+
} else if constexpr (PmtMap<T>) {
88+
fmt::format_to(ctx.out(), "{{");
89+
auto new_func = [&function](const auto new_arg) -> ret_type { return function(new_arg, function); };
90+
size_t i = 0;
91+
for (auto& [k, v]: arg) {
92+
fmt::format_to(ctx.out(), "{}: ", k);
93+
std::visit(new_func, v);
94+
if (i++ < arg.size() - 1)
95+
fmt::format_to(ctx.out(), ", ");
96+
}
97+
return fmt::format_to(ctx.out(), "}}");
98+
// When we drop support for gcc11/clang15, get rid of the nested lambda and replace
99+
// the above with this line.
100+
//return fmt::format_to(ctx.out(), "{{{}}}", fmt::join(arg, ", "));
101+
} else if constexpr (std::same_as<std::monostate, T>)
102+
return fmt::format_to(ctx.out(), "null");
103+
return fmt::format_to(ctx.out(), "unknown type {}", typeid(T).name());
104+
};
105+
return function_main(format_arg, function_main);
106+
};
107+
return std::visit(format_func, value);
108+
109+
}
110+
};
111+
112+
} // namespace fmt
113+
114+
namespace pmtv {
115+
template <IsPmt P>
116+
std::ostream& operator<<(std::ostream& os, const P& value) {
117+
os << fmt::format("{}", value);
118+
return os;
119+
}
120+
}

include/pmtv/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
files = [
2+
'format.hpp',
23
'pmt.hpp',
34
'rva_variant.hpp',
45
'type_helpers.hpp',

include/pmtv/pmt.hpp

+2-125
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@
2121
#pragma GCC diagnostic pop
2222
#endif
2323

24-
// Support for std::format is really spotty.
25-
// Gcc12 does not support it.
26-
// Eventually replace with std::format when that is widely available.
27-
#include <fmt/format.h>
28-
2924
namespace pmtv {
3025

3126
using pmt = pmt_var_t;
@@ -528,7 +523,7 @@ T cast(const P& value)
528523
return std::visit(
529524
[](const auto& arg) -> T {
530525
using U = std::decay_t<decltype(arg)>;
531-
if constexpr (std::constructible_from<T, U>) {
526+
if constexpr (std::convertible_to<U, T> || (Complex<T> && Complex<U>)) {
532527
if constexpr(Complex<T>) {
533528
if constexpr (std::integral<U> || std::floating_point<U>) {
534529
return std::complex<typename T::value_type>(static_cast<typename T::value_type>(arg));
@@ -543,127 +538,9 @@ T cast(const P& value)
543538
// return std::get<std::map<std::string, pmt_var_t, std::less<>>>(arg);
544539
// }
545540
else
546-
throw std::runtime_error(fmt::format(
547-
"Invalid PMT Cast {} {}", typeid(T).name(), typeid(U).name()));
541+
throw std::runtime_error("Invalid PMT Cast " + std::string(typeid(T).name()) + " " + std::string(typeid(U).name()));
548542
},
549543
value);
550544
}
551545

552546
} // namespace pmtv
553-
554-
namespace fmt {
555-
template <>
556-
struct formatter<pmtv::map_t::value_type> {
557-
template <typename ParseContext>
558-
constexpr auto parse(ParseContext& ctx) {
559-
return ctx.begin();
560-
}
561-
562-
template <typename FormatContext>
563-
auto format(const pmtv::map_t::value_type& kv, FormatContext& ctx) const {
564-
return fmt::format_to(ctx.out(), "{}: {}", kv.first, kv.second);
565-
}
566-
};
567-
568-
template <pmtv::Complex C>
569-
struct formatter<C> {
570-
template <typename ParseContext>
571-
constexpr auto parse(ParseContext& ctx) {
572-
return ctx.begin();
573-
}
574-
575-
template <typename FormatContext>
576-
auto format(const C& arg, FormatContext& ctx) const {
577-
if (arg.imag() >= 0)
578-
return fmt::format_to(ctx.out(), "{0}+j{1}", arg.real(), arg.imag());
579-
else
580-
return fmt::format_to(ctx.out(), "{0}-j{1}", arg.real(), -arg.imag());
581-
}
582-
};
583-
584-
585-
template<pmtv::IsPmt P>
586-
struct formatter<P>
587-
{
588-
589-
template <typename ParseContext>
590-
constexpr auto parse(ParseContext& ctx) {
591-
return ctx.begin();
592-
}
593-
594-
template <typename FormatContext>
595-
auto format(const P& value, FormatContext& ctx) const {
596-
// Due to an issue with the c++ spec that has since been resolved, we have to do something
597-
// funky here. See
598-
// https://stackoverflow.com/questions/37526366/nested-constexpr-function-calls-before-definition-in-a-constant-expression-con
599-
// This problem only appears to occur in gcc 11 in certain optimization modes. The problem
600-
// occurs when we want to format a vector<pmt>. Ideally, we can write something like:
601-
// return fmt::format_to(ctx.out(), "[{}]", fmt::join(arg, ", "));
602-
// It looks like the issue effects clang 14/15 as well.
603-
// However, due to the above issue, it fails to compile. So we have to do the equivalent
604-
// ourselves. We can't recursively call the formatter, but we can recursively call a lambda
605-
// function that does the formatting.
606-
// It gets more complicated, because we need to pass the function into the lambda. We can't
607-
// pass in the lamdba as it is defined, so we create a nested lambda. Which accepts a function
608-
// as a argument.
609-
// Because we are calling std::visit, we can't pass non-variant arguments to the visitor, so we
610-
// have to create a new nested lambda every time we format a vector to ensure that it works.
611-
using namespace pmtv;
612-
using ret_type = decltype(fmt::format_to(ctx.out(), ""));
613-
auto format_func = [&ctx](const auto format_arg) {
614-
auto function_main = [&ctx](const auto arg, auto function) -> ret_type {
615-
using namespace pmtv;
616-
using T = std::decay_t<decltype(arg)>;
617-
if constexpr (Scalar<T> || Complex<T>)
618-
return fmt::format_to(ctx.out(), "{}", arg);
619-
else if constexpr (std::same_as<T, std::string>)
620-
return fmt::format_to(ctx.out(), "{}", arg);
621-
else if constexpr (UniformVector<T> || UniformStringVector<T>)
622-
return fmt::format_to(ctx.out(), "[{}]", fmt::join(arg, ", "));
623-
else if constexpr (std::same_as<T, std::vector<pmt>>) {
624-
fmt::format_to(ctx.out(), "[");
625-
auto new_func = [&function](const auto new_arg) -> ret_type { return function(new_arg, function); };
626-
for (auto& a: std::span(arg).first(arg.size()-1)) {
627-
std::visit(new_func, a);
628-
fmt::format_to(ctx.out(), ", ");
629-
}
630-
std::visit(new_func, arg[arg.size()-1]);
631-
return fmt::format_to(ctx.out(), "]");
632-
// When we drop support for gcc11/clang15, get rid of the nested lambda and replace
633-
// the above with this line.
634-
//return fmt::format_to(ctx.out(), "[{}]", fmt::join(arg, ", "));
635-
} else if constexpr (PmtMap<T>) {
636-
fmt::format_to(ctx.out(), "{{");
637-
auto new_func = [&function](const auto new_arg) -> ret_type { return function(new_arg, function); };
638-
size_t i = 0;
639-
for (auto& [k, v]: arg) {
640-
fmt::format_to(ctx.out(), "{}: ", k);
641-
std::visit(new_func, v);
642-
if (i++ < arg.size() - 1)
643-
fmt::format_to(ctx.out(), ", ");
644-
}
645-
return fmt::format_to(ctx.out(), "}}");
646-
// When we drop support for gcc11/clang15, get rid of the nested lambda and replace
647-
// the above with this line.
648-
//return fmt::format_to(ctx.out(), "{{{}}}", fmt::join(arg, ", "));
649-
} else if constexpr (std::same_as<std::monostate, T>)
650-
return fmt::format_to(ctx.out(), "null");
651-
return fmt::format_to(ctx.out(), "unknown type {}", typeid(T).name());
652-
};
653-
return function_main(format_arg, function_main);
654-
};
655-
return std::visit(format_func, value);
656-
657-
}
658-
};
659-
660-
} // namespace fmt
661-
662-
namespace pmtv {
663-
template <IsPmt P>
664-
std::ostream& operator<<(std::ostream& os, const P& value) {
665-
os << fmt::format("{}", value);
666-
return os;
667-
}
668-
}
669-

python/pmtv/bindings/pmt_python.cc

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
namespace py = pybind11;
3131

3232
#include <pmtv/pmt.hpp>
33+
#include <pmtv/format.hpp>
3334

3435
// pydoc.h is automatically generated in the build directory
3536
// #include <pmt_pydoc.h>

test/qa_map.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <string_view>
1212

1313
#include <pmtv/pmt.hpp>
14+
#include <pmtv/format.hpp>
1415

1516
using namespace pmtv;
1617

test/qa_scalar.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <complex>
1111

1212
#include <pmtv/pmt.hpp>
13+
#include <pmtv/format.hpp>
1314
#include <sstream>
1415

1516
using namespace pmtv;

test/qa_string.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <pmtv/pmt.hpp>
1111

1212
#include <fmt/core.h>
13+
#include <pmtv/format.hpp>
1314
#include <string>
1415

1516
using namespace pmtv;

test/qa_uniform_vector.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <complex>
1010

1111
#include <pmtv/pmt.hpp>
12+
#include <pmtv/format.hpp>
1213

1314
#include <list>
1415
#include <map>

test/qa_vector_of_pmts.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <map>
1313

1414
#include <pmtv/pmt.hpp>
15+
#include <pmtv/format.hpp>
1516

1617
#include <fmt/core.h>
1718

0 commit comments

Comments
 (0)