2

I'm currently struggling to convert fmt::format_string<Args...>to a std::string_view.

The idea: I would like to create a fucntion with can be called from an ISR and task context. However in an ISR no dynamic memory allocation is allowed. That's why i cannot call fmt::fomat() in this case. However I'm getting a strange compiler error

<source>(67): error C7595: 'fmt::v9::basic_format_string<char>::basic_format_string': call to immediate function is not a constant expression

Here is a minimal working example

#include <fmt/format.h>
#include <string>
#include <string_view>
#include <source_location>
#include <concepts>
#include <cassert>


class DebugQueue
{
public:
bool addToTaskQueue(
      std::string& strMessage,
      std::string& strCategeory,
      const std::source_location loc
    )
    {
        return false;
    };
    bool addToISRQueue(
      const std::string_view strFormatedDebugMessage,
      const std::string_view strCategory,
      const std::source_location loc)
      {
          return false;
      };
};

class Log
{
    public:
    template <typename... Args>
    bool logAsync(
      fmt::format_string<Args...> fmt,
      const std::string_view strCategory,
      Args&&... args
    );

    template <typename String_T>
    bool logAsync(
      String_T& strFormatedDebugMessage,
      String_T& strCategory,
      const std::source_location loc = std::source_location::current()
    );

    static bool inHandlerMode();
    private:
    DebugQueue m_queue;
};

bool Log::inHandlerMode()
{
    return false;
}

template<typename ...Args>
  bool Log::logAsync(
    fmt::format_string<Args...> fmt,
    const std::string_view strCategory,
    Args&&... args)
  {
    //if called from ISR we cannot call formatting functions since they will implicitly call new
    bool bRes = false;
    if (inHandlerMode())
    {
      fmt::basic_string_view<char> asStringView = fmt;
      bRes = logAsync(std::string_view(asStringView.data()), strCategory);
    }
    else
    {
      std::string strFormatedMessage = fmt::format(fmt, std::forward<Args>(args)...);
      std::string strCat(strCategory);
      bRes = logAsync(strFormatedMessage, strCat);
    }
  }

  template<typename String_T>
  bool Log::logAsync(
    String_T& strFormatedDebugMessage, 
    String_T& strCategory, 
    const std::source_location loc)
  {
    bool bRes = false;
    if (inHandlerMode())
    {
      //called from ISR, do not use any dynamic memory allocation
      //just add the unformated message to the queue
      bRes = m_queue.addToISRQueue(strFormatedDebugMessage, strCategory, loc);
    }
    else
    {
      //called from Task Context
      std::string strMsg;
      std::string strCat;
      if constexpr (std::same_as<std::string, String_T>)
      {
        std::swap(strMsg, strFormatedDebugMessage);
        std::swap(strCategory, strCat);
      }
      else
      {
        strMsg = std::string(strFormatedDebugMessage.data());
        strCategory = std::string(strCat.data());
      }
      bRes = m_queue.addToTaskQueue(strFormatedDebugMessage, strCategory, loc);
    }
  }


static Log g_log;

int main() {
  fmt::print("The answer is {}.", 42);
  g_log.logAsync("Info {}", "fooCat",1);
  g_log.logAsync("Info", "fooCat");
}

https://godbolt.org/z/d7jeTjT6f

thx for your help guys :)

JHeni
  • 455
  • 3
  • 12
  • I am not sure what you are trying to do here. But if you cannot make allocations on the heap, then make a custom stack allocator and use `fmt::format_to` where you specify the output. – ALX23z Nov 17 '22 at 13:46
  • OK, another try (my previous comments didn't get the code correctly): There is no issue with constructing the `std::string_view`. The problem is that your overload resolution is messed up. – user17732522 Nov 17 '22 at 13:55
  • I assume you want the line with the error to call the `String_T` overload. But you are trying to pass the non-`const` `std::string_view` temporary as the first argument and the `const` `strCategory` as the second, so that deduction of `String_T` will fail with a mismatch. Even after fixing that you will either have issues trying to pass rvalues to it or it will be selected for the calls with literals as a better match, depending on how you try to resolve it. So really it should probably just be delegated to a helper function with a different name. – user17732522 Nov 17 '22 at 13:55
  • Hey guys. Yes the first overload of logAsync() is called when the string to format contains formating args. The second overload of logAsync() is called when 1) there is nothing left to format or when the log message did not contain any format args. Basically the problem is that there are some cases when I canot call new. So you are suggesting to use fmt::format_to() However I canot gurantee that format_fo does not do any allocation internally. So is there no way to discard the formating args and just print the message without any format stuff? – JHeni Nov 17 '22 at 16:41
  • Do you have any suggestions how to skipp the formatting in some cases? – JHeni Nov 17 '22 at 16:41
  • 1
    @JHeni The first overload may also be called if `Args` is empty, which is part of your problem. Give the second overload a different name and call it instead of `logAsync`. Also replace `String_T&` with `String_T` in the parameters. – user17732522 Nov 17 '22 at 19:50

1 Answers1

1

as suggested by @user17732522 in the comments the answer is changing String_T& to String_T and renaming

template <typename String_T>
    bool logAsync(
      String_T& strFormatedDebugMessage,
      String_T& strCategory,
      const std::source_location loc = std::source_location::current()
    );

to

template <typename String_T>
    bool _logAsync(
      String_T& strFormatedDebugMessage,
      String_T& strCategory,
      const std::source_location loc = std::source_location::current()
    );

did solve my problem. Thx guys for your help:)

JHeni
  • 455
  • 3
  • 12