Search code examples
c++c++17visual-studio-2019wstringfmt

Is there anything else needed to format BSTR wrapper to wstring using fmt?


The project has a wrapper written for BSTR.

// WinString.cpp
#include <iostream>
#include <string>

#include <comdef.h>
#include <OleAuto.h>
#include <atlconv.h>
#include <atlbase.h>

#define FMT_HEADER_ONLY
#include <fmt/format.h>
#include <fmt/xchar.h>

#pragma warning(disable: 4996)


namespace Win
{
class WinString
    {
    public:
        WinString() : buffer{nullptr} {};


        explicit WinString(const std::wstring& src) : buffer{::SysAllocString(src.data())} {}

        explicit WinString(const std::string& src){
            buffer = SysAllocString(CA2W(src.c_str()));;
        }

        ~WinString()
        {
            if (buffer)
            {
                SysFreeString(buffer);
            }
        }

        std::wstring ToWstring() const
        {
            return buffer ? std::wstring{buffer, ::SysStringLen(buffer)} : std::wstring{};
        }

        std::string ToString() const
        {
            _bstr_t bstr_t(buffer, false);
            return std::string(bstr_t);
        }

    private:
        BSTR buffer;
    };
}

Using WinString type by default was giving the following error.

error C2338: Cannot format an argument. To make type T formattable provide a formatter<T> specialization:

This is my first time using this library so I read couple of use cases on writing custom formatter for User Defined Types.

  1. https://wgml.pl/blog/formatting-user-defined-types-fmt.html#specifying-formatting-for-user-defined-types
  2. https://github.com/CelestiaProject/Celestia/blob/master/src/cel3ds/3dsread.cpp

I added the following code in, in hopes that it will solve the problem, but it is still giving me same error.

namespace fmt{
    template<>
    struct formatter<Win::WinString>
    {
        template<typename ParseContext>
        constexpr auto parse(ParseContext& ctx)
        {
            return ctx.begin();
        }

        template <typename FormatContext>
        auto format(Win::WinString const& winstr, FormatContext& ctx)
        {
            return fmt::format_to(ctx.out(), L"{}", winstr.ToWstring());
        }
    };
}

Is my implementation wrong or am I missing something?

Update: I just added one constructor and a member function for std::string to see if it works in case of std::string and modified the format function as below and it worked for std::string

template <typename FormatContext>
auto format(Win::WinString const& winstr, FormatContext& ctx)
{
    return fmt::format_to(ctx.out(), "{}", winstr.ToString());
}

How can I make it work with wstring?

WinString is being passed to a function which tries to use it with wstring, I want to make it work the following way.

int main()
{
    const std::wstring& value{L"Something something dark side!"};
    Win::WinString comStr{value};
    std::wcout << fmt::format(L"The emperor {} says {}\n", 1, comStr);
}

Solution

  • formatter is specialized on the type you want to format and the code unit type which is char by default so to make it work with wide strings pass wchar_t as a second template parameter:

    template <>
    struct fmt::formatter<Win::WinString, wchar_t> {
      template <typename ParseContext>
      constexpr auto parse(ParseContext& ctx) {
        return ctx.begin();
      }
    
      template <typename FormatContext>
      auto format(const Win::WinString & winstr, FormatContext& ctx) {
        return fmt::format_to(ctx.out(), L"{}", winstr.ToWstring());
      }
    };