14
template<class Msg, class... Args>
std::wstring descf(Msg, Args&&... args) {
    std::wostringstream woss;

    owss << Msg << ". " << ... << " " << args << ": '" << args << "' ";//not legal at all

    //or

    owss << Msg << ". " << args[0] << ": '" << args[1] << "'  " << args[2] << ": '" << args[3] << "' "; //... pseudo code, and so on...
}

I know I can just use a list of pairs or something like that instead, but I'm interested in how to do this while keeping the syntax of the function to:

const auto formatted = descf(L"message", "arg1", arg1, "arg2", arg2);
TylerH
  • 20,799
  • 66
  • 75
  • 101
darune
  • 10,480
  • 2
  • 24
  • 62

5 Answers5

9

This is easy with a couple of helper functions that follow the following pattern.

void helper() {}

template <class T1, class T2, class ... T>
void helper(T1 t1, T2 t2, T ... t)
{
     do_single_pair(t1, t2);
     helper(t...);
}

This is not a fold expression but the net result is the same.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • will template recursion depth be different than with a fold expression ? or will it be the same – darune Dec 04 '19 at 12:21
  • 1
    @darune There is no inherent recursion with fold expressions... Fold expressions just formally expand to some expression (in that specific instantiation of the variadic template). – Max Langhof Dec 04 '19 at 12:21
9

You can use a fold expression! It's not the prettiest*, but it's shorter than all the non-fold solutions presented:

template<class T, class ... Args>
std::wstring descf(T msg, Args&&... args) {
    std::wostringstream owss;
    owss << msg << ". ";

    std::array<const char*, 2> tokens{": '", "' "};
    int alternate = 0;
    ((owss << args << tokens[alternate], alternate = 1 - alternate), ...);

    return owss.str();
}

Demo with sample output: https://godbolt.org/z/Gs8d2x

We perform a fold over the comma operator, where each operand is an output of one args and the alternating token, plus switching the token index (the latter two are combined with another comma operator).

*To a reader familiar with fold expressions (and the comma operator) this is probably the "best" code, but for everyone else it's utter gibberish, so use your own judgement whether you want to inflict this on your code base.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • I guess this could also work with a bool (if only pairing isneeded) ala. : b ^= true; and then perhaps tenary operator (b ? ": '", " : "' ") – darune Dec 04 '19 at 12:31
  • 1
    @darune Sure, there are other ways to express the alternation. I decided to separate the output/alternation logic from the actual token values, which the array accomplishes nicely. I dislike the implicit conversion from `bool` to `int` when indexing so I went with an actual `int` to toggle the state. And pre- vs postfix `++` takes extra mental cycles to verify (for me at least), while the separate `1 - ` can't really be misread. In short, I tried to keep this as readable as possible, but this is of course up to personal taste (or the applicable style guide). max66 condensed it much more. – Max Langhof Dec 04 '19 at 12:37
  • Using a `std::array` instead of a native array seems a pointless complication. – Deduplicator Dec 04 '19 at 15:08
  • @Deduplicator I disagree strongly, as I find `std::array` infinitely more readable than `const char**`. But again, this is _my_ best shot at readability around some pretty obscure syntax, you can do with it what you like in your own code. All I can do is give you the data point of what I consider readable. – Max Langhof Dec 04 '19 at 15:25
6

I suppose you can try with an index and a ternary operator.

Something as follows

template <typename ... Args>
std::wstring descf (std::wstring const & Msg, Args && ... args)
 {
   std::wostringstream woss;

   int i = 0;

   ((woss << Msg << ". "), ... ,(woss << args << (++i & 1 ? ": '" : "' ")));

   return woss.str();
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • @MaxLanghof This has the advantage(?) of easy extension to more separators. – Deduplicator Dec 04 '19 at 15:10
  • @Deduplicator I don't understand what you are referring to? Can you explain? – Max Langhof Dec 04 '19 at 15:28
  • @Deduplicator - Not clear to me what do you mean with "extension to more separators"... anyway... this solution is very similar to the accepted one; I don't think it's more or less extensible. I suppose that is a little (little! maybe the compiler optimize in the same way) lighter because avoid the use of a `std::array` (that, anyway, is a light class) but (so I think is preferable the accepted answer) is less readable. – max66 Dec 04 '19 at 17:37
2

The following code should do the trick. The parameter pack is expanded in an initializer list.

#include <string>
#include <iostream>
#include <sstream>
#include <vector>

template <typename...Args>
std::string descf(std::string msg, Args &&... args)
{
   auto argumentsVector = std::vector<std::string>{args...};

   std::stringstream ss;
   ss << msg << ". ";

   for (auto i = std::size_t{0}; i < argumentsVector.size() - 1; ++i)
      ss << argumentsVector[i] << ": '" << argumentsVector[i+1] << "' ";

   auto result = ss.str();
   if (!argumentsVector.empty())
       result.pop_back();
   return result;
}

int main()
{
   std::cout << descf("message", "arg1", "1", "arg2", "2") << std::endl;
}
1

With std::index_sequence:

template <class Msg, class... Pairs>
std::wstring descf_pair(const Msg& msg, const Pairs&... pairs)
{
    std::wstringstream woss;

    woss << msg << ". ";
    auto sep = L"";
    ((woss << sep << std::get<0>(pairs) << L": '"
                  << std::get<1>(pairs) << L"'", sep = L"  "), ...);
    return woss.str();
}

template <class Msg, std::size_t... Is, class Tuple>
decltype(auto) descf_impl(const Msg& msg, std::index_sequence<Is...>, Tuple&& t)
{
    return descf_pair(msg, std::tie(std::get<2 * Is>(t), std::get<2 * Is + 1>(t))...);
}

template <class Msg, typename ... Ts>
std::wstring descf(const Msg& msg, const Ts&... ts)
{
    static_assert(sizeof...(Ts) % 2 == 0);

    return descf_impl(msg,
                      std::make_index_sequence<sizeof...(Ts) / 2>(),
                      std::tie(ts...));
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302