How to format pointers using fmt library?
Asked Answered
T

1

10

I'm taking up some folks advice and looking into the fmt library: http://fmtlib.net

It appears to have the features I need, and claims to support %p (pointer), but when compiling my code which uses a %p I get a long string of template errors (incomprehensible). I'll post them at the end of this.

if I pull out the %p and the corresponding pointer argument, then it compiles on VS2017 c++17.

However, I'm at a loss as to how to either decode the template errors, or to get some insight as to why it won't accept a %p argument in the first place.

I've tried casting the argument to a (void*) - same issue.
I've tried using the python style syntax in the formatter {} - same issue.
I've broken out the %p bits separately from the rest of the formatting - same issue.

I see that there is support for user-types - but in this case I just want to output this as a raw pointer value. I could just skip it, after all how valuable can the pointer address be, really? But of course that means more work during conversion from sprintf to fmt::format to hunt down all %p and "do something with them" such as elide them.

But the docs seem to indicate that %p is supported - http://fmtlib.net/latest/syntax.html (about 3/4 of the way down - search on 'pointer' or 'p').

Here's the calling function: (note: pAccels is declared as const ACCEL *)

    m_hAccel = ::CreateAcceleratorTable(const_cast<LPACCEL>(pAccels), (int)count);
    if (!m_hAccel)
    {
        auto error = GetLastError();
        throw CWinAPIErrorException(__FUNCTION__, "CreateAcceleratorTable", fmt::format("%p,%u", pAccels, count), error);
    }

Here's the diagnostic spewage:

1>c:\users\steve\source\fmt\include\fmt\core.h(1073): error C2825: 'fmt::v5::internal::get_type<Context,Arg>::value_type': must be a class or namespace when followed by '::'
1>        with
1>        [
1>            Context=fmt::v5::basic_format_context<std::back_insert_iterator<fmt::v5::internal::buffer<char>>,char>,
1>            Arg=const ACCEL *
1>        ]
1>c:\users\steve\source\fmt\include\fmt\core.h(1081): note: see reference to class template instantiation 'fmt::v5::internal::get_type<Context,Arg>' being compiled
1>        with
1>        [
1>            Context=fmt::v5::basic_format_context<std::back_insert_iterator<fmt::v5::internal::buffer<char>>,char>,
1>            Arg=const ACCEL *
1>        ]
1>c:\users\steve\source\fmt\include\fmt\core.h(1190): note: see reference to function template instantiation 'unsigned __int64 fmt::v5::internal::get_types<Context,const ACCEL*,size_t>(void)' being compiled
1>        with
1>        [
1>            Context=fmt::v5::basic_format_context<std::back_insert_iterator<fmt::v5::internal::buffer<char>>,char>
1>        ]
1>c:\users\steve\source\fmt\include\fmt\core.h(1190): note: while compiling class template member function 'unsigned __int64 fmt::v5::format_arg_store<fmt::v5::basic_format_context<std::back_insert_iterator<fmt::v5::internal::buffer<Char>>,Char>,const ACCEL *,size_t>::get_types(void)'
1>        with
1>        [
1>            Char=char
1>        ]
1>c:\users\steve\source\fmt\include\fmt\core.h(1478): note: see reference to class template instantiation 'fmt::v5::format_arg_store<fmt::v5::basic_format_context<std::back_insert_iterator<fmt::v5::internal::buffer<Char>>,Char>,const ACCEL *,size_t>' being compiled
1>        with
1>        [
1>            Char=char
1>        ]
1>c:\users\steve\source\tbx\wapi\acceleratortable.cpp(58): note: see reference to function template instantiation 'std::basic_string<char,std::char_traits<char>,std::allocator<char>> fmt::v5::format<char[6],const ACCEL*,size_t,0>(const S (&),const ACCEL *const &,const size_t &)' being compiled
1>        with
1>        [
1>            S=char [6]
1>        ]
1>c:\users\steve\source\fmt\include\fmt\core.h(1073): error C2510: 'value_type': left of '::' must be a class/struct/union
1>c:\users\steve\source\fmt\include\fmt\core.h(1073): error C2065: 'type_tag': undeclared identifier
1>c:\users\steve\source\fmt\include\fmt\core.h(1073): error C2131: expression did not evaluate to a constant
1>c:\users\steve\source\fmt\include\fmt\core.h(1073): note: a non-constant (sub-)expression was encountered
1>c:\users\steve\source\fmt\include\fmt\core.h(1197): error C2131: expression did not evaluate to a constant
1>c:\users\steve\source\fmt\include\fmt\core.h(1082): note: failure was caused by non-constant arguments or reference to a non-constant symbol
1>c:\users\steve\source\fmt\include\fmt\core.h(1082): note: see usage of 'value'
Toluate answered 10/5, 2019 at 0:3 Comment(2)
You have to cast to void* first.Cottrell
Have you tested to see if %p is totally broken for pointers? What if you gave it a plain old int *? What would happen?Deterrent
P
20

To format a pointer you can either cast it to void*:

std::string s = fmt::format("{},{}", static_cast<void*>(pAccels), count);

or wrap it in fmt::ptr:

std::string s = fmt::format("{},{}", fmt::ptr(pAccels), count);

Working example on godbolt: https://godbolt.org/z/sCNbjr

Note that format uses Python-like format string syntax, not printf's and returns a std::string object.

Pneumograph answered 10/5, 2019 at 3:36 Comment(4)
It also supports printf style %s and etc. At least the docs say as much. I did try casting pAccels to void* and hit the same issue. But I hadn't run CMake - which might resolve this.Toluate
Yes, but for %s you need to use fmt::sprintf, not fmt::format.Pneumograph
You don't need CMake, check out the godbolt link that uses vanilla MSVC. The error might be because you are passing std::string to an API requiring a C string, not pointer formatting.Pneumograph
Not sure why - either I was tired, or the compiler was flagging later invocations of format() as erroneous due to previous invocations that it did not issue an error on? Dunno. At any rate, simply casting to void* was the trick. I needed to cast other handles to either void* or UINT_PTR depending on if I was trying to output them as %p or %X, respectively.Toluate

© 2022 - 2024 — McMap. All rights reserved.