Como fazer dobras agrupadas ou emparelhadas do pacote de parâmetros?

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

Eu sei que posso apenas usar uma lista de pares ou algo parecido, mas estou interessado em como fazer isso, mantendo a sintaxe da função:

const auto formatted = descf(L"message", "arg1", arg1, "arg2", arg2);
darune
fonte

Respostas:

9

Você pode usar uma expressão de dobra! Não é a mais bonita *, mas é mais curta do que todas as soluções não dobráveis ​​apresentadas:

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();
}

Demonstração com saída de amostra: https://godbolt.org/z/Gs8d2x

Realizamos uma dobra sobre o operador de vírgula, em que cada operando é uma saída de um argse o token alternativo, além de alternar o índice do token (os dois últimos são combinados com outro operador de vírgula).

* Para um leitor familiarizado com as expressões fold (e o operador de vírgula), esse provavelmente é o código "melhor", mas para todos os outros é totalmente sem sentido, então use seu próprio julgamento se você deseja infligir isso em sua base de código.

Max Langhof
fonte
Eu acho que isso também pode funcionar com um ala bool (se for necessário apenas emparelhamento). : b ^ = verdadeiro; e, em seguida, talvez operador de inquilino (b? ": '", ":"' ")
darune 04/12/19
11
@darune Claro, existem outras maneiras de expressar a alternância. Decidi separar a lógica de saída / alternância dos valores reais do token, que a matriz realiza de maneira adequada. Não gosto da conversão implícita de boolpara a intindexação, então fui com um real intpara alternar o estado. E o pré-vs postfix ++leva ciclos mentais extras para verificar (pelo menos para mim), enquanto o separado 1 - não pode realmente ser mal interpretado. Resumindo, tentei manter isso o mais legível possível, mas é claro que isso depende do gosto pessoal (ou do guia de estilo aplicável). O max66 condensou muito mais.
precisa
Usar uma std::arraymatriz em vez de uma matriz nativa parece uma complicação inútil.
Deduplicator
@Duplicador Não concordo totalmente, pois acho std::array<const char*, 2>infinitamente mais legível do que const char**. Mas, novamente, esta é minha melhor chance de legibilidade em torno de uma sintaxe bastante obscura; você pode fazer com ela o que quiser em seu próprio código. Tudo o que posso fazer é fornecer os dados do que considero legível.
precisa
9

Isso é fácil com algumas funções auxiliares que seguem o seguinte padrão.

void helper() {}

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

Esta não é uma expressão de dobra, mas o resultado líquido é o mesmo.

n. 'pronomes' m.
fonte
a profundidade de recursão do modelo será diferente da expressão de dobra? ou ele vai ser o mesmo
darune
11
@darune Não há recursão inerente com expressões de dobra ... As expressões de dobra são expandidas formalmente para alguma expressão (nessa instanciação específica do modelo variadic).
precisa
6

Suponho que você possa tentar com um índice e um operador ternário.

Algo da seguinte forma

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
fonte
@ MaxLanghof Isso tem a vantagem (?) De fácil extensão para mais separadores.
Deduplicator
@Duplicador Não entendo o que você está se referindo? Você pode explicar?
precisa
@ Reduplicator - Não está claro para mim o que você quer dizer com "extensão para mais separadores" ... de qualquer maneira ... essa solução é muito semelhante à aceita; Não acho que seja mais ou menos extensível. Suponho que seja um pouco (pouco! Talvez o compilador otimize da mesma maneira) mais leve, porque evite o uso de um std::array(que, de qualquer forma, é uma classe leve), mas (então eu acho que é preferível a resposta aceita) seja menos legível.
max66
2

O código a seguir deve fazer o truque. O pacote de parâmetros é expandido em uma lista de inicializadores.

#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
fonte
Isso requer que todos argssejam conversíveis em std::strings.
Walnut
@ Walnut, isso está correto. Se isso não puder ser um requisito, você terá que resultar em expressões / recursão de dobra
Mattias De Charleroy
1

Com 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
fonte