1

I have a function with a parameter pack, I pass this pack to the fmt::format function, and I want to create a formatStr according to the args count, meaning add "{}#" for each passed argument.

I can do it using iterating, but is it possible to do this with one line solution? How to do it gracefully?

    template <typename... Args>
    void formatArgs( Args&&...args)
    {
        const auto size = sizeof...(Args);
        std::string formatStr = ...// "{}#{}#{}..." - {}# should depend on args count
        
        /* 
        std::ostringstream formatStr;
        for (const auto& p : { args... })
            formatStr << {}#";
        */
    
        auto res = fmt::format(formatStr.c_str(), args...);
        ...
    }
Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
Hardwired
  • 804
  • 1
  • 8
  • 19
  • Why use `printf` here? Why not use `std::cout` to print the arguments? It's possible with [fold](https://en.cppreference.com/w/cpp/language/fold) expressions. Also see [this old question](https://stackoverflow.com/questions/51281811/print-spaces-between-each-element-using-a-fold-expression) for a few ways to add characters in between arguments. – Some programmer dude Sep 09 '22 at 11:46
  • Of use overloading to print (still with `std::cout`): One function for a single argument (to handle the last one) and one with one argument plus the parameter pack. Doing it this way should be plenty of examples for. – Some programmer dude Sep 09 '22 at 11:53
  • Ok, I don't use `printf`, I use `fmt::format` function and I need to create a string like "{}#{}#{}..." from the param pack – Hardwired Sep 09 '22 at 12:01
  • Is the use of `std::format` mandatory? Otherwise you could use `std::ostringstream` and parameter pack folding to create the string directly from the arguments. – Some programmer dude Sep 09 '22 at 12:08
  • 2
    Alexandr, I think you made it impossible for the people that know fmt intimately to find your question by posting this printf "distraction". Don't do such things! – Marcus Müller Sep 09 '22 at 12:16
  • @MarcusMüller I corrected my question, thanks! – Hardwired Sep 09 '22 at 12:19
  • hm why the format string? Does the `fmt::join` functionality not do what you want? – Marcus Müller Sep 09 '22 at 12:20
  • @Someprogrammerdude do you propose to iterate over this pack as in my example? – Hardwired Sep 09 '22 at 12:20
  • @MarcusMüller, could you please share an example with `fmt::join` ? – Hardwired Sep 09 '22 at 12:41
  • I'd try, but I don't know what your formatted string should look like in the end, or what the things in your args are. – Marcus Müller Sep 09 '22 at 14:56

3 Answers3

4

An ugly fold expression would work. It's only redeeming quality is that it's just one line, and in C++20 it should be a constexpr, so in C++20 this'll wind up to be a single std::string constructor call:

#include <string>
#include <iostream>

template<typename ...Args>
std::string string_from_args(Args && ...args)
{
    return std::string{ ((args, std::string{"%s#"}) + ...) };
}

int main()
{
    std::cout << string_from_args(1, 2, 3, 4) << "\n";

    return 0;
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
1

If you want to create a string with all the arguments I suggest you use an std::ostringstream to create the string directly instead of going through std::format or similar.

How to create the string differs between version of C++. Before C++17 and pre-fold you can use overloading of functions to handle the argument packs:

// Handle single-argument string construction
template<typename Arg>
std::string string_from_args(Arg&& arg)
{
    return (std::ostringstream() << arg).str();
}

// Handle multiple-argument string construction
template<typename First, typename ...Rest>
std::string string_from_args(First&& first, Rest&& ...rest)
{
    return (std::ostringstream() << string_from_args(first) << '#' << string_from_args(rest...)).str();
}

Then with fold-expressions introduced in C++17:

template<typename ...Args>
std::string string_from_args(Args&& ...args)
{
    char const* separator = "";
    std::ostringstream os;
    (((os << separator << args), separator = "#"), ...);
    return os.str();
}

With a simple main function

int main()
{
    std::cout << string_from_args(123, 456.789, "foo", 'b');
}

both variants should construct and print the string

123#456.789#foo#b
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
1

If all arguments have the same type which seems to be the case considering that your current solution involves iteration then you could use fmt::join. For example:

#include <fmt/ranges.h>

template <typename... Args>
std::string formatArgs(Args&&... args) {
  return fmt::format("{}", fmt::join({args...}, ""));
}

int main() {
  fmt::print("{}", formatArgs(1, 2, 3));
}

prints

123
vitaut
  • 49,672
  • 25
  • 199
  • 336