Search code examples
c++c++20variadic-templatesstdformat

How can I transform types from a variadic argument before sending to std::vformat


I want to transform values of specific types coming from a variadic argument before sending it to std::vformat() but I can't seem to find a way to make it happen.

I tried getting the format arguments but I can't change it's value since the std::format_args::get() returns a const value.

Also tried to figure a way to unpack the variadic, transform the values and repack to be used by std::vformat().

I also tried overriding the formatter for basic types but that doesn't get used in the end.

#include <format>
#include <string>
#include <iostream>
 

template <typename T>
inline void transform(T& valeur) {}
inline void transform(std::string& valeur) {
    valeur.append("abc");
}

inline void transformArgs() {}
template <typename T, typename... Args>
void transformArgs(T& t, Args&&... args) {

  transform(t);
  transformArgs(args...);
}


template <typename... Args>
std::string prepare(std::string requete, Args&&... args)
{
    //I want to add "abc" to any string-like values before using vformat
    std::format_args fmtargs = std::make_format_args(args...);

    const unsigned int nb = sizeof...(args);
    for (unsigned int i = 0; i < nb; ++i) {

        auto arg = fmtargs.get(i);
        //Can't do anything with arg
    }

    transformArgs(args...);

  try {
    return std::vformat(requete, std::make_format_args(args...));
  }
  catch (...) {}

  return ""; 
}

int main() {

  char fixed[] {"banana"};
  auto result = prepare("{} - {} - {} - {}", 5, "litteral string", fixed, std::string("litteraly a string"));
  std::cout << result.c_str();

  /*
    I would expect this output : 
    5 - litteral stringabc - bananaabc - litteraly a stringabc
  */
}
 

Here's a sketch of my attempts https://godbolt.org/z/red6654sf

Edit: Changed a thing to not get undefined behavior in the example.


Solution

  • I'd recommend changing the approach slightly, instead of having transform modify its argument, it should return a value that will replace in input.

    So you can have a set of overloads like this:

    std::string transform(std::string str) {
        return str.append("abc");
    }
    template<std::size_t N> std::string transform(const char(&arr)[N]) {
        return std::string{&arr[0], N - 1}.append("abc");
    }
    template<std::size_t N> std::string transform(char(&arr)[N]) {
        return std::string{&arr[0], N - 1}.append("abc");
    }
    template<typename T> T&& transform(T&& value) {
        return static_cast<T&&>(value);
    }
    

    And wrapp all the arguments in calls to transform when calling make_format_args:

    return std::vformat(requete, std::make_format_args(transform(args)...));
    

    Example on Compiler Explorer