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

Add vformat_to_n function #769

Closed
mabuchner opened this issue Jun 8, 2018 · 9 comments
Closed

Add vformat_to_n function #769

mabuchner opened this issue Jun 8, 2018 · 9 comments

Comments

@mabuchner
Copy link

mabuchner commented Jun 8, 2018

Just like there is a function called fmt::vformat_to, which takes a fmt::format_args as parameter, there should also be a fmt::vformat_to_n function, which takes a fmt::format_args as parameter.

Or is there any reason, why this function does not exist?

@vitaut
Copy link
Contributor

vitaut commented Jun 8, 2018

It is not possible to provide vformat_to_n that takes format_args because the former transforms the output iterator into the one that does truncation. However, it is possible to provide vformat_to_n version that takes a different basic_format_args. Could you explain your use case?

@mabuchner
Copy link
Author

I have a class which looks similar to this:

template <size_t N>
class FixedSizeString {
public:
    // ...

    void setFromFormatV(const char format[], fmt::format_args args) noexcept {
        fmt::vformat_to_n(&mData[0], N, format, args);
    }

    template <typename... Args>
    void setFromFormat(const char format[], const Args&... args) noexcept {
        setFromFormatV(format, fmt::make_format_args(args...));
    }

private:
    char mData[N];
};

It allows me to format a string into a fixed size buffer. Based on the report_error/ vreport_error functions example, I added one function template with variadic template parameters and one regular function with fmt::format_args as parameter. FixedSizeString::setFromFormatV now is supposed to be callable by other regular functions, which also receive a fmt::format_args object as parameter.

It's not a major issue, but I was just wondering, why fmt::vformat_to_n is missing. On first glance it appeared like an oversight.

@vitaut
Copy link
Contributor

vitaut commented Jun 9, 2018

The v overloads are optional and primarily to prevent code bloat. In your example it's enough to call format_to_n and the latter will take care of dispatching to the correct v function:

    template <typename... Args>
    void setFromFormat(const char format[], const Args&... args) noexcept {
        fmt::format_to_n(&mData[0], N, format, args...);
    }

@vitaut vitaut closed this as completed Jun 9, 2018
@mabuchner
Copy link
Author

But this means, that I have to use variadic template functions all over the place. If I want to use an abstract interface class such as the following, then this even isn't possible without implementing my own type erasure solution:

class FixedSizeStringBase {
public:
    virtual ~FixedSizeStringBase() = default;
    virtual void setFromFormat(const char format[], fmt::format_args args) noexcept = 0;
};

template <size_t N>
class FixedSizeString : public FixedSizeStringBase {
public:
    virtual ~FixedSizeString() = default;

    void setFromFormat(const char format[], fmt::format_args args) noexcept override {
        fmt::vformat_to_n(&mData[0], N, format, args);
    }

private:
    char mData[N];
};

(contrived example)

My previous solution used va_list and vsnprintf, which I now try to replace with fmt::format_args and fmt::vformat_to_n.

@vitaut vitaut reopened this Jun 12, 2018
@vitaut
Copy link
Contributor

vitaut commented Jun 17, 2018

Right now this can only be done using the internal API, but I can make the relevant parts of the API public if this works for you:

#include "fmt/format.h"

using format_to_n_context = typename fmt::format_context_t<
  fmt::internal::truncating_iterator<char*>>::type;
using format_to_n_args = fmt::basic_format_args<format_to_n_context>;

class FixedSizeStringBase {
 public:
  virtual void vsetFromFormat(const char *format, format_to_n_args args) = 0;

  template <typename... Args>
  void setFromFormat(const char *format, const Args&... args) {
    vsetFromFormat(format, fmt::make_format_args<format_to_n_context>(args...));
  }
};

template <size_t N>
class FixedSizeString : public FixedSizeStringBase {
 public:
  void vsetFromFormat(const char *format, format_to_n_args args) override {
    vformat_to(fmt::internal::truncating_iterator<char*>(&mData[0], N),
               format, args);
  }

 private:
  char mData[N];
};

int main() {
  FixedSizeString<10> s;
  s.setFromFormat("{}", 42);
}

@mabuchner
Copy link
Author

mabuchner commented Jun 19, 2018

Thanks, that already would help me.

So a vformat_to_n function might basically look like this:

template <typename OutputIt>
using format_to_n_context = typename fmt::format_context_t<
  fmt::internal::truncating_iterator<OutputIt>>::type;

template <typename OutputIt>
using format_to_n_args = fmt::basic_format_args<format_to_n_context<OutputIt> >;

template <typename OutputIt, typename ...Args>
inline format_arg_store<format_to_n_contex<OutputIt>, Args...>
    make_format_to_n_args(const Args & ... args) {
  return format_arg_store<format_to_n_contex<OutputIt>, Args...>(args...);
}

template <typename OutputIt, typename... Args>
inline format_to_n_result<OutputIt> vformat_to_n(
    OutputIt out, std::size_t n, string_view format_str, format_to_n_args<OutputIt> args) {
  typedef internal::truncating_iterator<OutputIt> It;
  auto it = vformat_to(It(out, n), format_str, args);
  return {it.base(), it.count()};
}

char buffer[128];
fmt::vformat_to_n(&buffer[0], 128, "{}, {}", make_format_to_n_args<char*>(42, 43));

It's kind of ugly, that the iterator type has to be specified for the make_format_to_n_args function template.

vitaut added a commit that referenced this issue Jun 23, 2018
@vitaut
Copy link
Contributor

vitaut commented Jun 23, 2018

Added vformat_to_n in c2f3805. Regarding passing the iterator type, it's possible to create an alias for contiguous iterators that isn't parameterized on the iterator type, but I'm not sure if it's worth it considering that this is an API for library developers, not end users.

@vitaut vitaut closed this as completed Jun 23, 2018
@mabuchner
Copy link
Author

mabuchner commented Jun 24, 2018

Thank you very much! Unfortunately I can't try those changes, because the current master branch produces the following linker error:

Undefined symbols for architecture x86_64:
  "int fmt::v5::internal::char_traits<char>::format_float<double>(char*, unsigned long, char const*, unsigned int, int, double)", referenced from:
      void fmt::v5::basic_writer<fmt::v5::output_range<fmt::v5::internal::truncating_iterator<char*>, char> >::write_double<double>(double, fmt::v5::basic_format_specs<char> const&) in test_FixedSizeString.cpp.o
  "int fmt::v5::internal::char_traits<char>::format_float<long double>(char*, unsigned long, char const*, unsigned int, int, long double)", referenced from:
      void fmt::v5::basic_writer<fmt::v5::output_range<fmt::v5::internal::truncating_iterator<char*>, char> >::write_double<long double>(long double, fmt::v5::basic_format_specs<char> const&) in test_FixedSizeString.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I haven't switch to vformat_to_n and I'm just calling format_to_n. I simply updated from 5.0.0 to the current master branch. I'm running MacOS with LLVM 6.0.0.

@mabuchner
Copy link
Author

Never mind. The problem was caused by my broken build configuration. I had two copies of fmt and for some reason it was using the header from one version, but the library from the other.

vformat_to_n appears to be working as expected. Thanks again.

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