Как сделать групповую или парную кратность набора параметров?

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

Я знаю, что могу просто использовать список пар или что-то в этом роде, но мне интересно, как это сделать, сохраняя при этом синтаксис функции:

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

person darune    schedule 04.12.2019    source источник


Ответы (5)


Вы можете использовать выражение сгиба! Это не самое красивое*, но короче всех представленных не складных решений:

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

Демонстрация с примером вывода: https://godbolt.org/z/Gs8d2x

Выполняем свертку над оператором запятой, где каждый операнд является выходом одного args и чередующейся лексемы, плюс переключение индекса лексемы (последние два объединяются еще одним оператором запятой).

*Для читателя, знакомого с выражениями fold (и оператором запятой), это, вероятно, "лучший" код, но для всех остальных это полная тарабарщина, так что решайте сами, хотите ли вы применить это к своей кодовой базе.

person Max Langhof    schedule 04.12.2019
comment
Я предполагаю, что это также может работать с логическим значением (если требуется только сопряжение). : б ^= истина; а затем, возможно, тенарный оператор (b ? : ', : ' ) - person darune; 04.12.2019
comment
@darune Конечно, есть и другие способы выразить чередование. Я решил отделить логику вывода/перемены от фактических значений токенов, с чем прекрасно справляется массив. Мне не нравится неявное преобразование из bool в int при индексировании, поэтому я использовал фактическое int для переключения состояния. И пре-постфикс ++ требует дополнительных умственных циклов для проверки (по крайней мере, для меня), в то время как отдельный 1 - действительно не может быть неправильно истолкован. Короче говоря, я старался сделать это как можно более читабельным, но это, конечно, зависит от личного вкуса (или применимого руководства по стилю). max66 сжал это намного больше. - person Max Langhof; 04.12.2019
comment
Использование std::array вместо собственного массива кажется бессмысленным усложнением. - person Deduplicator; 04.12.2019
comment
@Deduplicator Я категорически не согласен, так как считаю std::array<const char*, 2> бесконечно более читабельным, чем const char**. Но опять же, это мой лучший способ удобочитаемости с некоторым довольно неясным синтаксисом, вы можете делать с ним все, что хотите, в своем собственном коде. Все, что я могу сделать, это дать вам данные о том, что я считаю удобочитаемым. - person Max Langhof; 04.12.2019

Это легко сделать с помощью пары вспомогательных функций, которые следуют следующему шаблону.

void helper() {}

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

Это не кратное выражение, но конечный результат тот же.

person n. 1.8e9-where's-my-share m.    schedule 04.12.2019
comment
будет ли глубина рекурсии шаблона отличаться от выражения fold? или это будет то же самое - person darune; 04.12.2019
comment
@darune В выражениях сгиба нет присущей рекурсии ... Выражения сгиба просто формально расширяются до некоторого выражения (в этом конкретном экземпляре вариативного шаблона). - person Max Langhof; 04.12.2019

Я полагаю, вы можете попробовать с индексом и тернарным оператором.

Что-то вроде следующего

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();
 }
person max66    schedule 04.12.2019
comment
@MaxLanghof Преимущество этого (?) заключается в простом расширении до большего количества разделителей. - person Deduplicator; 04.12.2019
comment
@Deduplicator Я не понимаю, о чем ты говоришь? Вы можете объяснить? - person Max Langhof; 04.12.2019
comment
@Deduplicator - мне непонятно, что вы имеете в виду под расширением на большее количество разделителей ... в любом случае ... это решение очень похоже на принятое; Я не думаю, что это более или менее расширяемый. Я полагаю, что это немного (немного! Возможно, компилятор оптимизирует таким же образом) легче, потому что избегайте использования std::array (это, в любом случае, легкий класс), но (поэтому я думаю, что принятый ответ предпочтительнее) менее читаем . - person max66; 04.12.2019

Следующий код должен помочь. Пакет параметров раскрывается в списке инициализаторов.

#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;
}
person Mattias De Charleroy    schedule 04.12.2019
comment
Это требует, чтобы все args можно было преобразовать в std::strings. - person walnut; 04.12.2019
comment
@грецкий орех, это правильно. Если это не может быть требованием, вам придется свернуть выражения/рекурсию - person Mattias De Charleroy; 04.12.2019

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

Демо

person Jarod42    schedule 04.12.2019