Apakah lambdifikasi suatu konsep merupakan perbaikan atau praktik yang buruk?

Aug 20 2020

Tampaknya Anda dapat memasukkan lambda ke dalam konsep dan kemudian menulis kode di dalamnya. Mari kita ambil ini sebagai contoh. Saya akan lebih memilih konsep standar untuk konsep semacam itu dan ingatlah bahwa ini hanya untuk tujuan contoh ini - lambang pintu

template<class T>
concept labdified_concept =
    requires {
            [](){                 
                T t, tt; // default constructible
                T ttt{t}; // copy constructible
                tt = t; //copy assignable
                tt = std::move(t); // move assignable
            };
        };

Dari pada:

template<class T>
concept normal_concept = 
    std::default_initializable<T> && std::movable<T> && std::copy_constructible<T>;

Apakah lambdification merupakan perbaikan atau praktik yang buruk? Dari titik keterbacaan juga.

Jawaban

2 Barry Aug 21 2020 at 01:10

Ini seharusnya tidak valid. Inti dari lambda yang diizinkan ke dalam konteks yang tidak dievaluasi bukanlah untuk tiba-tiba mengizinkan SFINAE pada pernyataan.

Kami memiliki beberapa kata dalam [temp.deduct] / 9 yang menjelaskan hal ini:

Sebuah lambda ekspresi muncul dalam jenis fungsi atau parameter template tidak dianggap sebagai bagian dari konteks langsung untuk tujuan argumen template deduksi. [ Catatan : Tujuannya adalah untuk menghindari penerapan yang diperlukan untuk menangani kegagalan substitusi yang melibatkan pernyataan arbitrer. [ Contoh :

template <class T>
  auto f(T) -> decltype([]() { T::invalid; } ());
void f(...);
f(0);               // error: invalid expression not part of the immediate context

template <class T, std::size_t = sizeof([]() { T::invalid; })>
  void g(T);
void g(...);
g(0);               // error: invalid expression not part of the immediate context

template <class T>
  auto h(T) -> decltype([x = T::invalid]() { });
void h(...);
h(0);               // error: invalid expression not part of the immediate context

template <class T>
  auto i(T) -> decltype([]() -> typename T::invalid { });
void i(...);
i(0);               // error: invalid expression not part of the immediate context

template <class T>
  auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t));   // #1
void j(...);                                                            // #2
j(0);               // deduction fails on #1, calls #2

- contoh akhir ] - catatan akhir ]

Kami hanya tidak memiliki sesuatu yang setara untuk persyaratan. perilaku gcc benar-benar seperti yang Anda harapkan:

template <typename T> concept C = requires { []{ T t; }; };
struct X { X(int); };
static_assert(!C<X>); // ill-formed

Karena badan lambda berada di luar konteks langsung, jadi ini bukan kegagalan substitusi, ini adalah kesalahan yang sulit.

5 NicolBolas Aug 20 2020 at 21:58

Mengabaikan kelemahan keterbacaan yang jelas dalam mekanisme ini, itu tidak benar-benar berfungsi . Pertimbangkan hal berikut:

template<labdified_concept T>
void foo(T t) {}

template<typename T>
void foo(T t) {}

Aturan konsep memberi tahu kita bahwa jika suatu yang diberikan Ttidak memenuhi labdified_concept, maka yang lain fooharus dipakai sebagai gantinya. Tapi bukan itu yang terjadi jika kita menyediakan SStemplate seperti itu. Sebaliknya, kami mendapatkan kesalahan keras karena labdified_concept<SS>tidak dapat digunakan.

Hal-hal dalam requiresekspresi memiliki penanganan khusus yang memungkinkan jenis kesalahan tertentu dianggap sebagai kegagalan untuk memenuhi persyaratan. Tetapi penanganan itu tidak berlaku untuk tubuh lambda. Ada, kode sakit-terbentuk sakit-dibentuk dan dengan demikian Anda mendapatkan error kompilasi ketika mencoba untuk menurunkannya.

Dan bahkan jika berhasil, tetap saja tidak berhasil. Konsep memiliki aturan yang kompleks untuk subsumsi konsep, yang memungkinkan berbagai konsep dianggap lebih terspesialisasi daripada yang lain. Hal ini memungkinkan kelebihan beban pada konsep yang berbeda , yang memungkinkan konsep yang lebih terbatas dipanggil. Misalnya konsep yang hanya membutuhkan default_initializablelebih umum daripada yang membutuhkan default_initializabledan moveable. Jadi, jika suatu tipe memenuhi keduanya, yang terakhir akan diambil karena lebih dibatasi.

Tetapi ini hanya berfungsi karena aturan khusus untuk concepts. Menyembunyikan persyaratan di lambda tidak akan memungkinkan ini berfungsi.