Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for 'std::variant' in C++17 #2941

Merged
merged 6 commits into from
Jun 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The {fmt} library API consists of the following parts:
formatting functions and locale support
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
* :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/color.h <color-api>`: terminal color and text style
* :ref:`fmt/os.h <os-api>`: system APIs
Expand Down Expand Up @@ -181,8 +182,9 @@ Formatting User-defined Types

The {fmt} library provides formatters for many standard C++ types.
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
and time formatting.
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
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
template and implement ``parse`` and ``format`` methods::
Expand Down Expand Up @@ -447,6 +449,35 @@ The format syntax is described in :ref:`chrono-specs`.

.. 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:

Format string compilation
Expand Down
2 changes: 1 addition & 1 deletion doc/syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ points are:
| | 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.

.. range-specs:
Expand Down
95 changes: 95 additions & 0 deletions include/fmt/std.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#define FMT_STD_H_

#include <thread>
#include <type_traits>
#include <utility>

#include "ostream.h"

Expand Down Expand Up @@ -68,4 +70,97 @@ template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
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_
33 changes: 33 additions & 0 deletions test/std-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "fmt/std.h"

#include <string>

#include "gtest/gtest.h"

TEST(std_test, path) {
Expand All @@ -32,3 +34,34 @@ TEST(std_test, path) {
TEST(std_test, thread_id) {
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
}