#Checking whether type is formattable with `std::format`

10 messages · Page 1 of 1 (latest)

merry tulip
#

I'm trying to make a function to_string that will attempt to turn some value into a string using std::format. Sometimes this is possible, sometimes not, but the function should always compile and return a string. The naive attempt with requires fails:

timid ironBOT
#

When your question is answered use !solved or the button below to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

merry tulip
#

;compile -std=c++26

#include <format>
#include <string>
#include <cassert>

std::string to_string(auto value) {
    if constexpr (requires{std::format("{}", value);}) {
        return std::format("{}", value);
    }
    return "dunno";
}

struct U {};

struct R {};

template <>
struct std::formatter<R, char> {
    template <class ParseContext>
    constexpr ParseContext::iterator parse(ParseContext &ctx) {
        std::string arg;
        auto it = ctx.begin();
        for (; it != ctx.end() && *it != '}'; ++it) {
            arg.push_back(*it);
        }
        if (arg != "") {
            throw std::format_error{"Invalid format specifier \"" + arg + "\" for R"};
        }
        return it;
    }

    template <class FmtContext>
    FmtContext::iterator format(const R &, FmtContext &ctx) const {
        return std::format_to(ctx.out(), "R");
    }
};

struct S {};

template <>
struct std::formatter<S, char> {
    template <class ParseContext>
    constexpr ParseContext::iterator parse(ParseContext &ctx) {
        std::string arg;
        auto it = ctx.begin();
        for (; it != ctx.end() && *it != '}'; ++it) {
            arg.push_back(*it);
        }
        if (arg != "cool") {
            throw std::format_error{"Invalid format specifier \"" + arg + "\" for S"};
        }
        return it;
    }

    template <class FmtContext>
    FmtContext::iterator format(const S &, FmtContext &ctx) const {
        return std::format_to(ctx.out(), "S");
    }
};

int main() {
    assert(to_string(42) == "42"); //has a formatter
    assert(to_string(U{}) == "dunno"); //does not have a formatter
    assert(to_string(R{}) == "R"); //has a formatter
    assert(to_string(S{}) == "dunno"); //has a formatter, but does not support "{}"
}
vital yarrowBOT
#
Compiler Output
In file included from <source>:1:
/cefs/22/22e6cdc013c8541ce3d1548e_consolidated/compilers_c++_x86_gcc_15.2.0/include/c++/15.2.0/format: In instantiation of 'class std::__format::_Checking_scanner<char, U>':
/cefs/22/22e6cdc013c8541ce3d1548e_consolidated/compilers_c++_x86_gcc_15.2.0/include/c++/15.2.0/format:4809:4:   required from 'consteval std::basic_format_string<_CharT, _Args>::basic_format_string(const _Tp&) [with _Tp = char [3]; _CharT = char; _Args = {U&}]'
 4809 |           __scanner(_M_str);
      |           ^~~~~~~~~
<source>:7:28:   required from 'std::string to_string(auto:11) [with auto:11 = U; std::string = std::__cxx11::basic_string<char>]'
    7 |         return std::format("{}", value);
      |                            ^~~~
<source>:62:5:   required from here
   62 |     assert(to_string(U{}) == "dunno"); //does not have a formatter
      |            ~~~~~~~~~^~~~~
/cefs/22/22e6cdc013c8541ce3d1548e_consolidated/compilers_c++_x86_gcc_15.2.0/include/c++/15.2.0/fo
merry tulip
#

I've also tried using std::formatter<T, char> and std::format_context, but it always results in a hard error saying the formatter is not a constant expression due to the thrown exception.

austere quiver
#

do you just want std::formattable?

#

oh I see, you want to check specifically that it's formattable with {}; I'm not sure that's possible, because any such errors are outside of the immediate context

merry tulip
#

Yeah, std::formattable essentially fails the S case.

austere quiver
#

Ah, actually, I think by combining std::formattable with a requires on std::format_string it should work

#
template <typename T>
std::string to_string(T value) {
    if constexpr (std::formattable<T, char>) {
        if constexpr (requires {
            typename std::integral_constant<int, (std::format_string<T>("{}"), 1)>;
        }) {
            return std::format("{}", value);
        }
    }
    return "dunno";
}
``` works for me