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

std::optional #1367

Closed
berge2007 opened this issue Oct 15, 2019 · 15 comments
Closed

std::optional #1367

berge2007 opened this issue Oct 15, 2019 · 15 comments

Comments

@berge2007
Copy link

please support std::optionalso if it has no value substitute it with "NULL" or "(null)" (see: #417) or with a preconfigured value

@ghost
Copy link

ghost commented Oct 30, 2019

hi

@vitaut
Copy link
Contributor

vitaut commented Nov 12, 2019

You can dereference std::optional and format the value stored in it. Formatting a null optional would be inconsistent with the rest of {fmt}, but you can do something like:

fmt::format("{}", value_or_null(opt));

where value_or_null returns an object that does the formatting you want via the extension API.

@vitaut vitaut closed this as completed Nov 12, 2019
@jasonbeach
Copy link

This seems to work as a solution to this:

namespace fmt{
template <typename T>
struct formatter<std::optional<T>>:fmt::formatter<T> {

  template <typename FormatContext>
  auto format(const std::optional<T>& opt, FormatContext& ctx) {
    if (opt) {
      fmt::formatter<T>::format(*opt, ctx);
      return ctx.out();
    } 
    return fmt::format_to(ctx.out(), "NO VALUE");
  }
};
}

@jcelerier
Copy link
Contributor

jcelerier commented Jun 30, 2022

@jasonbeach sadly this does not really scale: I'm currently having the issue with two libraries which both include their own specialization of fmt::formatter<optional<T>> which breaks entirely

@vitaut
Copy link
Contributor

vitaut commented Jun 30, 2022

Now that we have a formatter for variant I think it would be reasonable to provide a formatter for optional. A PR is welcome.

@jcelerier
Copy link
Contributor

I wonder, what'd be a good thing to print when the optional is unset? The least surprising with variant printing "monostate" would be "nullopt", would that be good ?

@vitaut
Copy link
Contributor

vitaut commented Jun 30, 2022

nullopt seems reasonable.

@jasonbeach
Copy link

@jasonbeach sadly this does not really scale: I'm currently having the issue with two libraries which both include their own specialization of fmt::formatter<optional<T>> which breaks entirely

I would say that's a good argument to include it in the library...so downstream libraries don't have to, thereby introducing the possibilities of clashes. I can put a PR together it just might be a couple days before I'm able to.

@jasonbeach
Copy link

jasonbeach commented Jul 3, 2022

nullopt seems reasonable.

My only concern with that is that in log files it would kind of blend in with surrounding text. I think I would prefer something like <NO VALUE>, but I guess that might not make it readily obvious to whoever is looking at it that there's an optional involved, so maybe <NULL OPTIONAL>. I might try to play around and see if I can add a parse function to allow a user to supply what is formatted when no value is present.

@davawen
Copy link

davawen commented Aug 29, 2022

I quickly implemented a formatter which allows applying further manipulation depending on wether the optional is present.
I'm not sure how compliant it is with the library's rules (ie: the use of vformat or <> for markup), but it works if anyone needs something similar.

template <typename T>
struct formatter<std::optional<T>> {
	std::string_view underlying_fmt;
	std::string_view or_else;

	constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
		// {:<if present{}><if not present>}
		auto it = ctx.begin(), end = ctx.end();
		auto get_marker = [&it, end]() constexpr -> std::string_view {
			if(it == end || *it != '<') return std::string_view(nullptr, 0); // no match
			auto start = ++it;

			while(it != end && (*it++ != '>'));
			if(it == end) throw fmt::format_error("invalid format, unfinished range");

			return std::string_view(start, it - 1);
		};

		underlying_fmt = "{}";
		or_else = "";

		auto first = get_marker();
		if(first.data() != nullptr) underlying_fmt = first;

		auto second = get_marker();
		if(second.data() != nullptr) or_else = second;

		// Check if reached the end of the range:
		if (it != end && *it != '}') throw fmt::format_error("invalid format, no end bracket");
		return it;

	}

	template <typename FormatContext>
	auto format(const std::optional<T>& p, FormatContext& ctx) const -> decltype(ctx.out()) {
		if(p.has_value()) {
			return vformat_to(ctx.out(), underlying_fmt, format_arg_store<FormatContext, T>{*p});
		}
		else {
			return format_to(ctx.out(), "{}", or_else);
		}
	}
};

Here's how you would use it:

std::optional<int> arbitrary_optional;
fmt::print("{:<Value: {}><(NO VALUE)>}", arbitrary_optional);
// With a value -> "Value: 10"
// With nullopt -> "(NO VALUE)"

@tom-huntington
Copy link
Contributor

tom-huntington commented Feb 10, 2023

I like rust's convention

fmt::print("{}\n", std::optional{1});
// Some(1)
fmt::print("{}\n", std::optional<int>{});
// None

I like to print ranges of tuples containing optionals. Some(...) and None helps navigate the confusion. I think opt(...) and nullopt wouldn't be as pretty. Maybe opt(...) and opt(null) would be a nice solution.

@vitaut
Copy link
Contributor

vitaut commented Feb 10, 2023

As in Rust we probably want different representations for the empty and non-empty case to avoid ambiguity (opt(null) can be interpreted as an optional containing value printed as "null").

@ingo-loehken
Copy link

How do you like to handle format specifiers with precision or other expected output format to optionals that are nullopt ? For me thats an format_error, because requested precision or width cannot be guranteed.

@tom-huntington
Copy link
Contributor

Do you have code snippet for what's not working?

fmt::print("{:.2f}", std::optional<double>{});

works for me

@ingo-loehken
Copy link

Its just about expectations.

If I define (as a developer) a format, where I'd expect a floating point type and get a monostate representation type as a string, I would be confused. We have discussed that in depth in our company, because we have fmt specialized for std::optional.

But lets start with an example

Default PresentationType Behavior

fmt::print("{}", std::optional<double>{}); // i.e "(nullopt)"

I think it can be argued, that some kind of representation for undefined/not-initialized is ok. We choose, due to application logic, an empty string. A json-like representation would be "null", a technical representation "std::nullopt" or alike.

Specific PresentationType Behavior

fmt::print("{:.2f}", std::optional<double>{}); // "(nullopt)"
fmt::print("{:.2f}", std::optional<double>{1.234}); // "1.23"

As a developer I would expect a floating point and might get a string, that does not represent a floating point type. This is difficult to teach. Throwing an exception maybe the other solution. The question is: what is least suprising way to handle this and does no break expected integrity of data produced by format.

We also discussed, if the developer may also state, that an undefined/not-initialized is expected, but think, that it would degenerate the mini-dsl to specify the format-string and ppl get used to abusing the dsl.

For me the conclusion is: It is an application level specific problem not a library specific one. At least, if it is provided by the library, a opt-in would be nice, but afaik thats a behavior change crossing module boundaries and can be interpreted as ABI break.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants