Bagaimana cara membuat paket parameter yang dikelompokkan atau dipasangkan?

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

Saya tahu saya bisa menggunakan daftar pasangan atau sesuatu seperti itu sebagai gantinya, tetapi saya tertarik pada cara melakukan ini sambil menjaga sintaks fungsi untuk:

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

Jawaban:

9

Anda dapat menggunakan ekspresi lipatan! Ini bukan yang tercantik *, tetapi lebih pendek dari semua solusi non-lipat yang disajikan:

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 dengan output sampel: https://godbolt.org/z/Gs8d2x

Kami melakukan lipatan atas operator koma, di mana setiap operan adalah output dari satu argsdan token bergantian, ditambah beralih indeks token (dua yang terakhir digabungkan dengan operator koma lainnya).

* Untuk pembaca yang terbiasa dengan ekspresi lipatan (dan operator koma) ini mungkin kode "terbaik", tetapi untuk semua orang itu adalah omong kosong, jadi gunakan penilaian Anda sendiri apakah Anda ingin menerapkan ini pada basis kode Anda.

Max Langhof
sumber
Saya kira ini juga bisa bekerja dengan bool (jika hanya pasangan perlu) ala. : b ^ = true; dan kemudian mungkin operator tenary (b? ": '", ":"' ")
darune
1
@ Darune Tentu, ada cara lain untuk mengekspresikan pergantian. Saya memutuskan untuk memisahkan logika output / pergantian dari nilai-nilai token aktual, yang dicapai dengan baik oleh array. Saya tidak suka konversi implisit dari boolke intketika mengindeks jadi saya pergi dengan sebenarnya intuntuk beralih negara. Dan pre-vs postfix ++membutuhkan siklus mental ekstra untuk memverifikasi (setidaknya untuk saya), sedangkan yang terpisah 1 - tidak dapat benar-benar salah dibaca. Singkatnya, saya mencoba untuk menjaga ini agar dapat dibaca, tetapi ini tentu saja sesuai dengan selera pribadi (atau panduan gaya yang berlaku). max66 lebih kental.
Max Langhof
Menggunakan std::arraybukan array asli tampaknya komplikasi yang tidak ada gunanya.
Deduplicator
@Dupuplikator Saya sangat tidak setuju, karena saya merasa std::array<const char*, 2>jauh lebih mudah dibaca daripada const char**. Tetapi sekali lagi, ini adalah kesempatan terbaik saya untuk membaca di sekitar beberapa sintaks yang cukup jelas, Anda dapat melakukannya dengan apa yang Anda suka dalam kode Anda sendiri. Yang bisa saya lakukan adalah memberi Anda titik data dari apa yang saya anggap dapat dibaca.
Max Langhof
9

Ini mudah dengan beberapa fungsi pembantu yang mengikuti pola berikut.

void helper() {}

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

Ini bukan ekspresi lipat tetapi hasil bersihnya sama.

n. 'kata ganti' m.
sumber
akankah kedalaman rekursi templat berbeda dari dengan ekspresi lipatan? atau akan sama
darune
1
@ Darune Tidak ada rekursi yang melekat dengan ekspresi lipatan ... Lipat ekspresi yang secara formal diperluas ke beberapa ekspresi (dalam contoh spesifik templat variadic).
Max Langhof
6

Saya kira Anda dapat mencoba dengan indeks dan operator ternary.

Sesuatu sebagai berikut

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();
 }
maks66
sumber
@ MaxLanghof Ini memiliki keuntungan (?) Ekstensi mudah ke lebih banyak pemisah.
Deduplicator
@Dupuplikator Saya tidak mengerti apa yang Anda maksud? Bisakah Anda jelaskan?
Max Langhof
@Deduplicator - Tidak jelas bagi saya apa yang Anda maksud dengan "ekstensi ke lebih banyak pemisah" ... pokoknya ... solusi ini sangat mirip dengan yang diterima; Saya kira itu kurang lebih bisa diperluas. Saya kira itu sedikit (sedikit! Mungkin kompiler mengoptimalkan dengan cara yang sama) lebih ringan karena hindari penggunaan std::array(yang, bagaimanapun, adalah kelas ringan) tetapi (jadi saya pikir lebih disukai jawaban yang diterima) kurang dapat dibaca.
Maks66
2

Kode berikut harus melakukan trik. Paket parameter diperluas dalam daftar penginisialisasi.

#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
sumber
Ini mengharuskan semua argsdapat dikonversi ke std::strings.
walnut
@walnut, itu benar. Jika ini bukan keharusan, maka Anda harus melipat ekspresi / rekursi
Mattias De Charleroy
1

Dengan 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
sumber