Bagaimana tipe yang tidak ditentukan dapat digunakan dalam ekspresi 'membutuhkan' C ++ 20?

Aug 16 2020

Saya mencoba menulis konsep C ++ 20 untuk menyatakan persyaratan bahwa suatu tipe memiliki metode tertentu, yang membutuhkan argumen, tetapi untuk tujuan konsep ini saya tidak peduli apa tipe argumennya.

Saya sudah mencoba menulis sesuatu seperti:

template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

namun, baik gcc maupun clang menolak ini, memberikan kesalahan bahwa 'auto' tidak dapat digunakan dalam daftar parameter ekspresi yang membutuhkan dengan cara ini.

Alternatifnya adalah menempatkan jenis 'x' sebagai parameter template kedua:

template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

tetapi ini membutuhkan TX untuk ditentukan secara eksplisit setiap kali konsep tersebut digunakan, itu tidak dapat disimpulkan:

struct S { void Foo(int); };
static_assert(HasFooMethod<S>);         // doesn't compile
static_assert(HasFooMethod<S, int>);    // the 'int' must be specified

Adakah cara untuk menulis konsep yang memungkinkan Foo mengambil argumen dengan tipe yang tidak ditentukan ?

Pertanyaan Definisi konsep yang membutuhkan fungsi anggota templat yang dibatasi sangat mirip, tetapi tidak sama: pertanyaan itu menanyakan bagaimana mensyaratkan bahwa metode (templated) dapat mengambil tipe apa pun yang memenuhi konsep yang diberikan, sementara pertanyaan ini adalah tentang mengharuskan suatu metode beberapa tipe tertentu, meskipun tipe itu tidak ditentukan. Dalam hal bilangan, pertanyaan lain adalah menanyakan tentang kuantifikasi universal (terbatas), sedangkan yang ini adalah tentang kuantifikasi eksistensial. Jawaban pertanyaan lain juga tidak berlaku untuk kasus saya.

Jawaban

4 NicolBolas Aug 17 2020 at 13:48

Konsep tidak dimaksudkan untuk menyediakan jenis fungsionalitas yang Anda cari. Jadi mereka tidak menyediakannya.

Konsep dimaksudkan untuk membatasi template, untuk menentukan sekumpulan ekspresi atau pernyataan yang ingin digunakan oleh template (atau setidaknya bebas digunakan) dalam definisinya.

Di dalam template yang begitu membatasi, jika Anda menulis ekspresi t.Foo(x), maka Anda tahu jenisnya x. Ini bisa berupa jenis konkret, parameter template, atau nama yang diturunkan dari parameter template. Apa pun pilihannya, jenis xtersedia di template yang dibatasi.

Jadi jika Anda ingin membatasi template seperti itu, Anda menggunakan tipe dari tdan tipe x. Keduanya tersedia untuk Anda saat itu, jadi tidak ada masalah dengan membuat batasan seperti itu. Artinya, batasan tidak aktif Tsebagai tipe yang terisolasi; itu tentang hubungan antara Tdan X.

Konsep tidak dimaksudkan untuk bekerja dalam ruang hampa, tanpa hubungan apa pun dengan tempat penggunaan kendala yang sebenarnya. Anda tidak boleh fokus pada pembuatan konsep unary sehingga pengguna dapat static_assertmembuat kelasnya sendiri. Konsep tidak dimaksudkan untuk menguji jika suatu tipe memenuhinya (yang pada dasarnya adalah apa yang Anda static_assertlakukan); mereka dimaksudkan untuk membatasi definisi template yang menggunakannya .

Batasan Anda haruslah FooCallableWith, bukan HasFooMethod.

NathanReed Aug 17 2020 at 16:30

Sesuatu yang mendekati ini dapat dicapai dengan menentukan jenis adaptor yang secara implisit dapat mengonversi menjadi (hampir) apa pun:

struct anything
{
    // having both these conversions allows Foo's argument to be either
    // a value, an lvalue reference, or an rvalue reference

    template <typename T>
    operator T&();

    template <typename T>
    operator T&&();
};

Perhatikan bahwa operator ini tidak perlu diimplementasikan, karena mereka hanya akan digunakan dalam konteks yang tidak dievaluasi (dan memang, mereka tidak dapat diimplementasikan untuk semua tipe T).

Kemudian, HasFooMethoddapat ditulis sebagai:

template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
    { t.Foo(a) } -> std::same_as<void>;
};