Jak zrobić zgrupowane lub sparowane złożenie pakietu parametrów?

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...
}

Wiem, że zamiast tego mogę po prostu użyć listy par lub czegoś podobnego, ale interesuje mnie to, jak to zrobić, zachowując składnię funkcji w celu:

const auto formatted = descf(L"message", "arg1", arg1, "arg2", arg2);
darune
źródło

Odpowiedzi:

9

Możesz użyć wyrażenia krotnie! Nie jest najładniejszy *, ale jest krótszy niż wszystkie przedstawione rozwiązania niepasujące:

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 z przykładowym wyjściem: https://godbolt.org/z/Gs8d2x

Wykonujemy fold nad operatorem przecinkowym, gdzie każdy operand jest wyjściem jednego argsi przemiennego tokena, plus przełączanie indeksu tokenu (dwa ostatnie są łączone z innym operatorem przecinkowym).

* Dla czytelnika znającego wyrażenia fold (i przecinek) jest to prawdopodobnie „najlepszy” kod, ale dla wszystkich innych jest to kompletny bełkot, więc użyj własnego osądu, czy chcesz nałożyć go na bazę kodu.

Max Langhof
źródło
Myślę, że to może również działać z boolem (jeśli potrzebne jest tylko parowanie) ala. : b ^ = prawda; a następnie być może operator tenary (b? ":" ",": "'")
darune
1
@darune Pewnie, istnieją inne sposoby wyrażenia alternacji. Postanowiłem oddzielić logikę wyjściową / naprzemienną od rzeczywistych wartości tokenów, które tablica ładnie osiąga. Nie podoba mi się niejawna konwersja od booldo intpodczas indeksowania, więc zdecydowałem się na rzeczywistą intzmianę stanu. A przedrostek po kontra ++wymaga dodatkowych cykli myślowych (przynajmniej dla mnie), podczas gdy oddzielny 1 - tak naprawdę nie może być źle odczytany. Krótko mówiąc, starałem się, aby było to jak najbardziej czytelne, ale zależy to oczywiście od osobistego gustu (lub odpowiedniego przewodnika po stylu). max66 skondensował to znacznie więcej.
Max Langhof,
Używanie std::arrayzamiast rodzimej tablicy wydaje się bezcelową komplikacją.
Deduplicator,
@Deduplicator Nie zgadzam się zdecydowanie, ponieważ uważam, że jest std::array<const char*, 2>nieskończenie bardziej czytelny niż const char**. Ale znowu, to moja najlepsza szansa na czytelność wokół jakiejś dość niejasnej składni, możesz zrobić z tym, co chcesz we własnym kodzie. Wszystko, co mogę zrobić, to podać punkt danych tego, co uważam za czytelne.
Max Langhof,
9

Jest to łatwe dzięki kilku funkcjom pomocniczym, które są zgodne z następującym wzorcem.

void helper() {}

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

To nie jest wyrażenie krotnie, ale wynik netto jest taki sam.

n. „zaimki” m.
źródło
czy głębokość rekurencji szablonu będzie inna niż w przypadku wyrażenia krotnie? czy będzie tak samo
darune
1
@darune Nie ma nieodłącznej rekurencji z wyrażeniami składania ... Wyrażenia składania po prostu formalnie rozwijają się do wyrażenia (w tej konkretnej instancji szablonu variadic).
Max Langhof,
6

Podejrzewam, że możesz spróbować z indeksem i operatorem trójskładnikowym.

Coś w następujący sposób

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
źródło
@MaxLanghof Ma to tę zaletę (?), Że umożliwia łatwe rozszerzenie na więcej separatorów.
Deduplicator,
@Deduplicator Nie rozumiem o czym mówisz? Możesz wytłumaczyć?
Max Langhof,
@Deduplicator - Nie jest dla mnie jasne, co masz na myśli przez „rozszerzenie na więcej separatorów” ... w każdym razie ... to rozwiązanie jest bardzo podobne do przyjętego; Nie sądzę, że jest to mniej więcej rozszerzalne. Przypuszczam, że jest to trochę (trochę! Być może kompilator optymalizuje w ten sam sposób) lżejsze, ponieważ unikaj używania std::array(to w każdym razie klasy lekkiej), ale (tak myślę, że lepiej jest zaakceptowana odpowiedź) jest mniej czytelna.
max66
2

Poniższy kod powinien załatwić sprawę. Pakiet parametrów jest rozwijany na liście inicjalizatora.

#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;
}
Mattias De Charleroy
źródło
To wymaga, argsaby wszystko można było zamienić na std::strings.
orzech
@walnut, to prawda. Jeśli nie może to być wymagane, musisz spasować wyrażenia / rekurencję
Mattias De Charleroy
1

Z 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...));
}

Próbny

Jarod42
źródło