Mencocokkan keberadaan dan tanda tangan fungsi anggota: parameter

Membaca pertanyaan terkait "Cara memanggil anggota berfungsi hanya jika objek kebetulan memilikinya?" dan "Apakah mungkin menulis template C++ untuk memeriksa keberadaan suatu fungsi?", saya mengimplementasikan kelas ciri saya sendiri. Tujuannya sangat sederhana, meskipun saya tidak dapat mencapai apa yang saya inginkan: menyediakan kelas ciri yang secara statis mengalihkan panggilan ke kelas yang cocok.

Jadi, jika kelas yang saya berikan pada ciri-ciri saya memiliki, misalnya metode void open_file(), ia akan memanggilnya, jika tidak, gunakan fungsi ciri-ciri (yang NOP, tetapi sekarang merupakan keluaran). Jelas sekali, ini adalah tugas SFINAE, namun karena tidak terlalu familiar dengan prosesnya, saya mengikuti ide-idenya, seperti yang akan Anda lihat.

Semuanya berfungsi dengan baik untuk void open_file(), tetapi ketika mencoba void open_file(int), tidak cocok dan memanggil fungsi NOP. Ini adalah upaya saya (hampir kata demi kata dari dua pertanyaan itu!):

template <class Type>
class my_traits
{
    //! Implements a type for "true"
    typedef struct { char value;    } true_class;
    //! Implements a type for "false"
    typedef struct { char value[2]; } false_class;

    //! This handy macro generates actual SFINAE class members for checking event callbacks
    #define MAKE_MEMBER(X) \
        public: \
            template <class T> \
            static true_class has_##X(decltype(&T::X)); \
        \
            template <class T> \
            static false_class has_##X(...); \
        public: \
            static constexpr bool call_##X = sizeof(has_##X<Type>(0)) == sizeof(true_class);

    MAKE_MEMBER(open_file)

public:

    /* SFINAE foo-has-correct-sig :) */
    template<class A, class Buffer>
    static std::true_type test(void (A::*)(int) const)
    {
        return std::true_type();
    }

    /* SFINAE foo-exists :) */
    template <class A>
    static decltype(test(&A::open_file)) test(decltype(&A::open_file), void *)
    {
        /* foo exists. What about sig? */
        typedef decltype(test(&A::open_file)) return_type;
        return return_type();
    }

    /* SFINAE game over :( */
    template<class A>
    static std::false_type test(...)
    {
        return std::false_type();
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<Type>(0, 0)) type;

    static const bool value = type::value; /* Which is it? */

    /*  `eval(T const &,std::true_type)`
     delegates to `T::foo()` when `type` == `std::true_type`
     */
    static void eval(Type const & t, std::true_type)
    {
        t.open_file();
    }
    /* `eval(...)` is a no-op for otherwise unmatched arguments */
    static void eval(...)
    {
        // This output for demo purposes. Delete
        std::cout << "open_file() not called" << std::endl;
    }

    /* `eval(T const & t)` delegates to :-
     - `eval(t,type()` when `type` == `std::true_type`
     - `eval(...)` otherwise
     */
    static void eval(Type const &t)
    {
        eval(t, type());
    }

};


class does_match
{
public:
    void open_file(int i) const { std::cout << "MATCHES!" << std::endl; };
};

class doesnt_match
{
public:
    void open_file() const { std::cout << "DOESN'T!" << std::endl; };
};

Seperti yang Anda lihat, saya telah menerapkan keduanya, yang pertama dengan makro MAKE_MEMBER hanya memeriksa keberadaan anggota, dan berhasil. Selanjutnya, saya mencoba menggunakannya untuk SFINAE if/else statis, yaitu, jika ada fungsi anggota, panggil saja, jika tidak gunakan fungsi yang telah ditentukan sebelumnya, tidak berhasil (seperti yang saya katakan, saya tidak terlalu mendalami ke dalam SFINAE).

Upaya kedua hampir sama persis dengan pertanyaan periksa tanda tangan dan keberadaan, namun saya telah memodifikasinya untuk menangani parameter. Namun, ini tidak akan berhasil:

    does_match   it_does;
    doesnt_match it_doesnt;

    my_traits<decltype(it_does)>::eval(it_does);
    my_traits<decltype(it_doesnt)>::eval(it_doesnt);

    // OUTPUT:
    // open_file() not called
    // open_file() not called

Jelas, ada masalah di sini: Saya tidak memberikan parameter, tetapi saya tidak tahu bagaimana melakukannya.

Saya mencoba memahami dan mempelajari, juga, dapatkah saya menggunakan open_file() yang bergantung pada parameter templat, misalnya memiliki SFINAE yang cocok dengan template <class T> open_file(T t)?

Terima kasih & bersorak!


person senseiwa    schedule 24.12.2014    source sumber


Jawaban (1)


Masalahnya adalah panggilan Anda ke test(&A::open_file):

typedef decltype(test(&A::open_file)) return_type;

selalu cocok dengan:

static std::false_type test(...)

karena ujian sebenarnya Anda memiliki parameter templat tipe yang tidak terdeduksi Buffer:

template<class A, class Buffer>
//                      ~~~~~^
static std::true_type test(void (A::*)(int) const)

karena itu, fungsi ini tidak pernah dianggap sebagai fungsi yang layak, kecuali Anda memberikan argumen tipe tersebut secara eksplisit, atau menghapusnya (apa yang harus dilakukan di sini).

Memperbaiki ini masih tidak akan menyelesaikan semua masalah kode Anda, karena Anda tidak memiliki fungsi cadangan yang dapat dipilih jika fungsi anggota open_file tidak ada sama sekali, jadi Anda perlu menambahkan sesuatu seperti di bawah ini (disesuaikan untuk implementasi Anda):

/* SFINAE foo-not-exists */
template <class A>
static std::false_type test(void*, ...);

sebagai cadangan untuk:

static decltype(test(&A::open_file)) test(decltype(&A::open_file), void *)

Kiat: Anda tidak perlu memberikan isi fungsi yang hanya muncul dalam konteks yang tidak dievaluasi seperti dalam operator decltype().

Terakhir, saat Anda akhirnya mencocokkan panggilan dengan tanda tangan void (A::*)(int) const, Anda sepertinya melupakan argumennya:

t.open_file(1);
//          ^

Uji:

my_traits<decltype(it_does)>::eval(it_does);
my_traits<decltype(it_doesnt)>::eval(it_doesnt);

Keluaran:

MATCHES!
open_file() not called

DEMO


Keseluruhan sifat dapat disederhanakan dengan menggunakan ekspresi SFINAE:

template <class Type>
struct my_traits
{
    template <typename T>
    static auto eval(const T& t, int) -> decltype(void(t.open_file(1)))
    {
        t.open_file(1);
    }

    template <typename T>
    static void eval(const T& t, ...)
    {
        std::cout << "open_file() not called" << std::endl;
    }

    static void eval(const Type& t)
    {
        eval<Type>(t, 0);
    }
};

my_traits<decltype(it_does)>::eval(it_does);     // MATCHES!
my_traits<decltype(it_doesnt)>::eval(it_doesnt); // open_file() not called

DEMO 2


Pertanyaan Bonus

Apakah mungkin untuk menggeneralisasi pendekatan ini? Misalnya, jika ada fungsi f, gunakan SFINAE untuk mencocokkannya, menggunakan kode yang Anda posting di DEMO 2, dan meneruskan parameter dari kode pengguna (mis., my_traits::eval(it_does, parameter, parameter))?

template <typename T, typename... Args>
static auto call(T&& t, int, Args&&... args)
    -> decltype(void(std::forward<T>(t).open_file(std::forward<Args>(args)...)))
{
    std::forward<T>(t).open_file(std::forward<Args>(args)...);
}

template <typename T, typename... Args>
static void call(T&& t, void*, Args&&... args)
{
    std::cout << "open_file() not called" << std::endl;
}

template <typename T, typename... Args>
static void eval(T&& t, Args&&... args)
{
    call(std::forward<T>(t), 0, std::forward<Args>(args)...);
}

eval(it_does, 1);    // MATCHES!
eval(it_doesnt, 2);  // open_file() not called
eval(it_does);       // open_file() not called
eval(it_doesnt);     // DOESN'T!

DEMO 3

person Piotr Skotnicki    schedule 24.12.2014
comment
Opsi kedua sangat menarik! Apakah menurut Anda mungkin untuk menggeneralisasi pendekatan ini? Misalnya, jika ada fungsi apa pun f gunakan SFINAE untuk mencocokkannya, menggunakan kode yang Anda posting di DEMO 2, dan meneruskan parameter dari kode pengguna (misalnya, my_traits<decltype(it_does)>::eval(it_does, parameter, parameter))? - person senseiwa; 24.12.2014
comment
Terima kasih @piotr-s, itu luar biasa! Jika saya tidak salah dengan melihat perakitan rilis, tidak ada overhead runtime, karena saya hanya melihat invoke void @_ZNK3seq10does_match9open_fileEi(%"class.seq::does_match"* %it_does, i32 4321), namun, saya melihat beberapa panggilan ke call void @llvm.dbg.value(metadata !{%"class.seq::does_match"* %it_does}, i64 0, metadata !13069), !dbg !13059, tapi saya pikir itu tidak akan mempengaruhi kode saya. Apakah saya menafsirkan ini ke arah yang benar? Terima kasih banyak! - person senseiwa; 26.12.2014