Add support for 'std::variant' in C++17 (#2941)
Add support for 'std::variant' in C++17. For C++17, if all the alternatives of a variant are formattable the variant is now also formattable. In addition 'std::monostate' is now formattable. Moves implementation into 'std.h', and tests into 'std-test.cc'. Avoid fold-expression since MSVC was crashing. Add section for 'fmt/std.h' in API-docs.
This commit is contained in:
parent
51535866d0
commit
6a775e9560
35
doc/api.rst
35
doc/api.rst
@ -12,6 +12,7 @@ The {fmt} library API consists of the following parts:
|
|||||||
formatting functions and locale support
|
formatting functions and locale support
|
||||||
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
|
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
|
||||||
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
|
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
|
||||||
|
* :ref:`fmt/std.h <std-api>`: formatters for standard library types
|
||||||
* :ref:`fmt/compile.h <compile-api>`: format string compilation
|
* :ref:`fmt/compile.h <compile-api>`: format string compilation
|
||||||
* :ref:`fmt/color.h <color-api>`: terminal color and text style
|
* :ref:`fmt/color.h <color-api>`: terminal color and text style
|
||||||
* :ref:`fmt/os.h <os-api>`: system APIs
|
* :ref:`fmt/os.h <os-api>`: system APIs
|
||||||
@ -184,8 +185,9 @@ Formatting User-defined Types
|
|||||||
|
|
||||||
The {fmt} library provides formatters for many standard C++ types.
|
The {fmt} library provides formatters for many standard C++ types.
|
||||||
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
|
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
|
||||||
containers such as ``std::vector`` and :ref:`fmt/chrono.h <chrono-api>` for date
|
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
|
||||||
and time formatting.
|
and time formatting and :ref:`fmt/std.h <std-api>` for filesystem and variant
|
||||||
|
formatting.
|
||||||
|
|
||||||
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
|
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
|
||||||
template and implement ``parse`` and ``format`` methods::
|
template and implement ``parse`` and ``format`` methods::
|
||||||
@ -445,6 +447,35 @@ The format syntax is described in :ref:`chrono-specs`.
|
|||||||
|
|
||||||
.. doxygenfunction:: gmtime(std::time_t time)
|
.. doxygenfunction:: gmtime(std::time_t time)
|
||||||
|
|
||||||
|
.. _std-api:
|
||||||
|
|
||||||
|
Standard Library Types Formatting
|
||||||
|
=================================
|
||||||
|
|
||||||
|
``fmt/std.h`` provides formatters for:
|
||||||
|
|
||||||
|
* `std::filesystem::path <std::filesystem::path>`_
|
||||||
|
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
|
||||||
|
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
|
||||||
|
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
|
||||||
|
|
||||||
|
Formatting Variants
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
A ``std::variant`` is only formattable if every variant alternative is formattable, and requires the
|
||||||
|
``__cpp_lib_variant`` `library feature <https://en.cppreference.com/w/cpp/feature_test>`_.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
#include <fmt/std.h>
|
||||||
|
|
||||||
|
std::variant<char, float> v0{'x'};
|
||||||
|
// Prints "variant('x')"
|
||||||
|
fmt::print("{}", v0);
|
||||||
|
|
||||||
|
std::variant<std::monostate, char> v1;
|
||||||
|
// Prints "variant(monostate)"
|
||||||
|
|
||||||
.. _compile-api:
|
.. _compile-api:
|
||||||
|
|
||||||
Format string compilation
|
Format string compilation
|
||||||
|
@ -346,7 +346,7 @@ points are:
|
|||||||
| | command ``%OS`` produces the locale's alternative representation. |
|
| | command ``%OS`` produces the locale's alternative representation. |
|
||||||
+---------+--------------------------------------------------------------------+
|
+---------+--------------------------------------------------------------------+
|
||||||
|
|
||||||
Specifiers that have a calendaric component such as `'d'` (the day of month)
|
Specifiers that have a calendaric component such as ``'d'`` (the day of month)
|
||||||
are valid only for ``std::tm`` and not durations or time points.
|
are valid only for ``std::tm`` and not durations or time points.
|
||||||
|
|
||||||
.. range-specs:
|
.. range-specs:
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
#define FMT_STD_H_
|
#define FMT_STD_H_
|
||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "ostream.h"
|
#include "ostream.h"
|
||||||
|
|
||||||
@ -68,4 +70,97 @@ template <typename Char>
|
|||||||
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_variant
|
||||||
|
# include <variant>
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
template <typename Char> struct formatter<std::monostate, Char> {
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const std::monostate&, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
auto out = ctx.out();
|
||||||
|
out = detail::write<Char>(out, "monostate");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using variant_index_sequence =
|
||||||
|
std::make_index_sequence<std::variant_size<T>::value>;
|
||||||
|
|
||||||
|
// variant_size and variant_alternative check.
|
||||||
|
template <typename T, typename U = void>
|
||||||
|
struct is_variant_like_ : std::false_type {};
|
||||||
|
template <typename T>
|
||||||
|
struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
// formattable element check
|
||||||
|
template <typename T, typename C> class is_variant_formattable_ {
|
||||||
|
template <std::size_t... I>
|
||||||
|
static std::conjunction<
|
||||||
|
is_formattable<std::variant_alternative_t<I, T>, C>...>
|
||||||
|
check(std::index_sequence<I...>);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr const bool value =
|
||||||
|
decltype(check(variant_index_sequence<T>{}))::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char, typename OutputIt, typename T>
|
||||||
|
auto write_variant_alternative(OutputIt out, const T& v) -> OutputIt {
|
||||||
|
if constexpr (is_string<T>::value)
|
||||||
|
return write_escaped_string<Char>(out, detail::to_string_view(v));
|
||||||
|
else if constexpr (std::is_same_v<T, Char>)
|
||||||
|
return write_escaped_char(out, v);
|
||||||
|
else
|
||||||
|
return write<Char>(out, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename T> struct is_variant_like {
|
||||||
|
static constexpr const bool value = detail::is_variant_like_<T>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename C> struct is_variant_formattable {
|
||||||
|
static constexpr const bool value =
|
||||||
|
detail::is_variant_formattable_<T, C>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Variant, typename Char>
|
||||||
|
struct formatter<
|
||||||
|
Variant, Char,
|
||||||
|
std::enable_if_t<std::conjunction_v<
|
||||||
|
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const Variant& value, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
auto out = ctx.out();
|
||||||
|
|
||||||
|
out = detail::write<Char>(out, "variant(");
|
||||||
|
std::visit(
|
||||||
|
[&](const auto& v) {
|
||||||
|
out = detail::write_variant_alternative<Char>(out, v);
|
||||||
|
},
|
||||||
|
value);
|
||||||
|
*out++ = ')';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // FMT_STD_H_
|
#endif // FMT_STD_H_
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include "fmt/std.h"
|
#include "fmt/std.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
TEST(std_test, path) {
|
TEST(std_test, path) {
|
||||||
@ -32,3 +34,34 @@ TEST(std_test, path) {
|
|||||||
TEST(std_test, thread_id) {
|
TEST(std_test, thread_id) {
|
||||||
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
|
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(std_test, variant) {
|
||||||
|
#ifdef __cpp_lib_variant
|
||||||
|
EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");
|
||||||
|
using V0 = std::variant<int, float, std::string, char>;
|
||||||
|
V0 v0(42);
|
||||||
|
V0 v1(1.5f);
|
||||||
|
V0 v2("hello");
|
||||||
|
V0 v3('i');
|
||||||
|
EXPECT_EQ(fmt::format("{}", v0), "variant(42)");
|
||||||
|
EXPECT_EQ(fmt::format("{}", v1), "variant(1.5)");
|
||||||
|
EXPECT_EQ(fmt::format("{}", v2), "variant(\"hello\")");
|
||||||
|
EXPECT_EQ(fmt::format("{}", v3), "variant('i')");
|
||||||
|
|
||||||
|
struct unformattable {};
|
||||||
|
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
|
||||||
|
EXPECT_FALSE((fmt::is_formattable<std::variant<unformattable>>::value));
|
||||||
|
EXPECT_FALSE((fmt::is_formattable<std::variant<unformattable, int>>::value));
|
||||||
|
EXPECT_FALSE((fmt::is_formattable<std::variant<int, unformattable>>::value));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
(fmt::is_formattable<std::variant<unformattable, unformattable>>::value));
|
||||||
|
EXPECT_TRUE((fmt::is_formattable<std::variant<int, float>>::value));
|
||||||
|
|
||||||
|
using V1 = std::variant<std::monostate, std::string, std::string>;
|
||||||
|
V1 v4{};
|
||||||
|
V1 v5{std::in_place_index<1>, "yes, this is variant"};
|
||||||
|
|
||||||
|
EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)");
|
||||||
|
EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user