Skip to content

Commit

Permalink
Add support for 'std::variant' in C++17.
Browse files Browse the repository at this point in the history
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.

The value of a variant is enclosed in '<' and '>', and the monostate
is formatted as ' '.
  • Loading branch information
jehelset committed Jun 18, 2022
1 parent 907a07c commit fd0cb68
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 3 deletions.
31 changes: 29 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/variant.h <variant-api>`: formatting of variants
* :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/variant.h <variant-api>` for 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,31 @@ The format syntax is described in :ref:`chrono-specs`.

.. doxygenfunction:: gmtime(std::time_t time)

.. _variant-api:

Variant Formatting
==================

``fmt/variant.h`` provides formatters for

* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_

**Example**::

#include <fmt/variant.h>

std::variant<char, float> v0{'x'};
// Prints "<'x'>"
fmt::print("{}", v0);

std::variant<std::monostate, char> v1{};
// Prints "< >"

.. note::

Variant support is only available for C++17 and up.

.. _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
107 changes: 107 additions & 0 deletions include/fmt/variant.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Formatting library for C++ - experimental range support
//
// {fmt} support for variant interface.

#ifndef FMT_VARIANT_H_
#define FMT_VARIANT_H_

#include <type_traits>
#include <variant>

#include "format.h"
#include "ranges.h"

FMT_BEGIN_NAMESPACE

#define FMT_HAS_VARIANT FMT_CPLUSPLUS >= 201703L

#if FMT_HAS_VARIANT

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 = format_context>
auto format(const std::monostate&, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
*out++ = ' ';
return out;
}
};

namespace detail {

template <typename T>
using variant_index_sequence = make_index_sequence<std::variant_size<T>::value>;

// variant_size and variant_alternative check.
template <typename T> class is_variant_like_ {
template <typename U>
static auto check(U* p) -> decltype(std::variant_size<U>::value, int());
template <typename> static void check(...);

public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};

// formattable element check
template <typename T, typename C, bool = is_variant_like_<T>::value>
class is_variant_formattable_ {
public:
static constexpr const bool value = false;
};
template <typename T, typename C> class is_variant_formattable_<T, C, true> {
template <std::size_t... I>
static std::integral_constant<
bool,
(fmt::is_formattable<std::variant_alternative_t<I, T>, C>::value && ...)>
check(index_sequence<I...>);

public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};

} // 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 VariantT, typename Char>
struct formatter<
VariantT, Char,
enable_if_t<fmt::is_variant_like<VariantT>::value &&
fmt::is_variant_formattable<VariantT, Char>::value>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}

template <typename FormatContext = format_context>
auto format(const VariantT& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
*out++ = '<';
std::visit(
[&](const auto& v) { out = detail::write_range_entry<Char>(out, v); },
value);
*out++ = '>';
return out;
}
};

#endif // FMT_HAS_VARIANT

FMT_END_NAMESPACE

#endif // FMT_VARIANT_H_
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ if (MSVC)
endif()
add_fmt_test(printf-test)
add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(variant-test)
add_fmt_test(scan-test)
add_fmt_test(std-test)
if (MSVC)
Expand Down
46 changes: 46 additions & 0 deletions test/variant-test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Formatting library for C++ - experimental variant API
//
// {fmt} support for variant interface.

#include "fmt/variant.h"

#include <string>

#include "gtest/gtest.h"

#if FMT_HAS_VARIANT

TEST(variant_test, format_monostate) {
EXPECT_EQ(fmt::format("{}", std::monostate{}), " ");
}
TEST(variant_test, format_variant) {
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), "<42>");
EXPECT_EQ(fmt::format("{}", v1), "<1.5>");
EXPECT_EQ(fmt::format("{}", v2), "<\"hello\">");
EXPECT_EQ(fmt::format("{}", v3), "<'i'>");

enum class noformatenum{b};
struct noformatstruct{};
EXPECT_FALSE((fmt::is_formattable<noformatenum>::value));
EXPECT_FALSE((fmt::is_formattable<noformatstruct>::value));
EXPECT_FALSE((fmt::is_formattable<std::variant<noformatenum>>::value));
EXPECT_FALSE((fmt::is_formattable<std::variant<noformatstruct>>::value));
EXPECT_FALSE((fmt::is_formattable<std::variant<noformatstruct,int>>::value));
EXPECT_FALSE((fmt::is_formattable<std::variant<int,noformatenum>>::value));
EXPECT_FALSE((fmt::is_formattable<std::variant<noformatstruct,noformatenum>>::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), "< >");
EXPECT_EQ(fmt::format("{}", v5), "<\"yes, this is variant\">");
}

#endif // FMT_HAS_VARIANT

0 comments on commit fd0cb68

Please sign in to comment.