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

fmt/printf.h with wchar_t* loses int type #1316

Closed
deadem opened this issue Sep 19, 2019 · 5 comments
Closed

fmt/printf.h with wchar_t* loses int type #1316

deadem opened this issue Sep 19, 2019 · 5 comments

Comments

@deadem
Copy link

deadem commented Sep 19, 2019

This code works just fine:
https://godbolt.org/z/x2BA0m

#include <fmt/format.h>
#include <boost/date_time/gregorian/gregorian.hpp>
...
boost::gregorian::greg_year year(2015);
std::wcout << fmt::format(L"{:d}", year);

But if I include <fmt/printf.h> instead of <format.h> I've got a runtime error:
https://godbolt.org/z/e2x3Sp

terminate called after throwing an instance of 'fmt::v6::format_error'
  what():  invalid type specifier

With char* formatting there is no error:
https://godbolt.org/z/Z80SwD

std::cout << fmt::format("{:d}", year);
@vitaut
Copy link
Contributor

vitaut commented Sep 19, 2019

The problem is that fmt/printf.h includes fmt/ostream.h which enables formatting of boost::gregorian::greg_year via operator<< and overrides implicit conversion to an integer. A condiiton should be added in

FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
disabling this overload if T is convertible to an integral type (or floating point type for that matter).

@vitaut
Copy link
Contributor

vitaut commented Sep 19, 2019

On a second thought, the current behavior is correct because one should be able to override implicit conversion. To fix the issue either don't include fmt/ostream.h or provide a specialization of formatter (https://godbolt.org/z/9D_krW):

#include <iostream>
#define FMT_HEADER_ONLY 1
#include <fmt/printf.h> // this should really be <fmt/format.h>
#include <boost/date_time/gregorian/gregorian.hpp>

namespace fmt {
template <typename Char>
struct formatter<boost::gregorian::greg_year, Char> : formatter<int, Char> {
  template <typename Context>
  auto format(boost::gregorian::greg_year y, Context& ctx) {
    return formatter<int, Char>::format(y, ctx);
  }
};
}

int main() {
  boost::gregorian::greg_year year(2015);
  std::wcout << fmt::format(L"{:d}", year);
}

@vitaut vitaut closed this as completed Sep 19, 2019
@deadem
Copy link
Author

deadem commented Sep 20, 2019

@vitaut: I have same thoughts until I discover that error disappears when I use char* instead of wchar_t*.

So

fmt::format(L"{:d}", year);

fails, but

fmt::format("{:d}", year);

not.

So I think there are question of consistency. I'am expect that both cases work or both cases fail. Why the problem exists only with wchar_t pointer?

@deadem
Copy link
Author

deadem commented Sep 20, 2019

And second question is why fmt try to deduce argument explicitly requested as int "{:d}" through operator<< even if int type can be inferred.

@vitaut vitaut reopened this Sep 22, 2019
vitaut added a commit that referenced this issue Sep 24, 2019
@vitaut
Copy link
Contributor

vitaut commented Sep 24, 2019

Why the problem exists only with wchar_t pointer?

Great question. As it turned out there is a difference in standard overload sets for char and wchar_t which together with a way operator<< detection worked in {fmt} caused the difference in behavior.

And second question is why fmt try to deduce argument explicitly requested as int "{:d}" through operator<< even if int type can be inferred.

It's mostly for enums since people may want to override implicit conversion with operator<<. In this particular case there is no user-defined operator<< and therefore formatting should go through the implicit conversion.

Should be fixed in ccc8f5d.

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

2 participants