การจับคู่การดำรงอยู่ของฟังก์ชันสมาชิกและลายเซ็น: พารามิเตอร์

การอ่านคำถามที่เกี่ยวข้อง "วิธีโทรหาสมาชิก ทำงานเฉพาะในกรณีที่วัตถุมีมันหรือไม่" และ "เป็นไปได้ไหมที่จะเขียนเทมเพลต C++ เพื่อตรวจสอบการมีอยู่ของฟังก์ชัน" ฉันกำลังใช้คลาสคุณลักษณะของตัวเอง วัตถุประสงค์นั้นง่ายมาก แม้ว่าฉันไม่สามารถบรรลุสิ่งที่ฉันต้องการได้: จัดเตรียมคลาสลักษณะที่เปลี่ยนเส้นทางการเรียกไปยังคลาสที่ตรงกันแบบคงที่

ดังนั้น หากคลาสที่ฉันระบุให้กับคุณลักษณะของฉันมี เช่น วิธีการ void open_file() มันก็เรียกมัน มิฉะนั้น ให้ใช้ฟังก์ชันคุณลักษณะ (a NOP หนึ่ง แต่ตอนนี้เป็นเอาต์พุต) แน่นอนว่านี่เป็นงานของ SFINAE แต่เนื่องจากไม่คุ้นเคยกับกระบวนการนี้มากนัก ฉันจึงได้ปฏิบัติตามแนวคิดต่างๆ ดังที่คุณจะเห็น

ทุกอย่างทำงานได้ดีสำหรับ void open_file() แต่เมื่อลอง void open_file(int) มันกลับไม่ตรงกันและเรียกใช้ฟังก์ชัน NOP นี่คือความพยายามของฉัน (เกือบจะเป็นคำต่อคำจากคำถามสองข้อนั้น!):

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

อย่างที่คุณเห็นฉันได้ใช้ทั้งสองอย่าง อันแรกที่มีมาโคร MAKE_MEMBER เพียงตรวจสอบการมีอยู่ของสมาชิก และมันใช้งานได้ ต่อไป ฉันพยายามใช้สำหรับ SFINAE if/else แบบคงที่ เช่น หากมีฟังก์ชันสมาชิกอยู่แล้วให้เรียกมัน มิฉะนั้น ให้ใช้ฟังก์ชันที่กำหนดไว้ล่วงหน้า โดยไม่ประสบความสำเร็จ (อย่างที่ฉันบอก ฉันไม่ได้ลึกเกินไป เข้าสู่ SFINAE)

ความพยายามครั้งที่สองเกือบจะเป็นคำต่อคำจากคำถาม ตรวจสอบลายเซ็นและการมีอยู่ แต่ฉันได้แก้ไขเพื่อจัดการพารามิเตอร์ อย่างไรก็ตาม มันจะไม่ทำงาน:

    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

แน่นอนว่ามีปัญหาเกิดขึ้นที่นี่ ฉันไม่ได้ระบุพารามิเตอร์ แต่ฉันไม่รู้ว่าจะทำอย่างไร

ฉันกำลังพยายามทำความเข้าใจและเรียนรู้ นอกจากนี้ ฉันสามารถใช้ open_file() ที่ขึ้นอยู่กับพารามิเตอร์เทมเพลต เช่น การจับคู่ SFINAE template <class T> open_file(T t) ได้หรือไม่

ขอบคุณและไชโย!


person senseiwa    schedule 24.12.2014    source แหล่งที่มา


คำตอบ (1)


ปัญหาคือคุณโทรไปที่ test(&A::open_file):

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

จะจับคู่กับ:

static std::false_type test(...)

เนื่องจาก การทดสอบจริง ของคุณมีพารามิเตอร์เทมเพลตประเภทที่ยังไม่ได้อนุมาน Buffer:

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

ด้วยเหตุนี้จึงไม่ถือว่าเป็นฟังก์ชันที่ทำงานได้ เว้นแต่คุณจะให้อาร์กิวเมนต์ประเภทนั้นอย่างชัดเจนหรือลบออก (สิ่งที่ควรทำที่นี่)

การแก้ไขนี้ยังคงไม่สามารถแก้ปัญหาโค้ดของคุณได้ทั้งหมด เนื่องจากคุณไม่มีฟังก์ชันสำรองที่สามารถเลือกได้หากไม่มีฟังก์ชันสมาชิก open_file เลย ดังนั้นคุณจะต้องเพิ่มบางอย่างด้านล่าง (ปรับแล้ว เพื่อการใช้งานของคุณ):

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

เป็นทางเลือกสำหรับ:

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

เคล็ดลับ: คุณไม่จำเป็นต้องระบุเนื้อความของฟังก์ชันที่ปรากฏเฉพาะในบริบทที่ไม่ได้รับการประเมิน เช่น ภายในตัวดำเนินการ decltype()

ในที่สุด เมื่อคุณจับคู่การโทรกับลายเซ็น void (A::*)(int) const ในที่สุด ดูเหมือนว่าคุณจะลืมข้อโต้แย้ง:

t.open_file(1);
//          ^

ทดสอบ:

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

ผลลัพธ์:

MATCHES!
open_file() not called

สาธิต


ลักษณะทั้งหมดสามารถทำให้ง่ายขึ้นได้มากโดยใช้ นิพจน์ 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

สาธิต 2


คำถามพิเศษ

เป็นไปได้ไหมที่จะสรุปแนวทางนี้? ตัวอย่างเช่น กำหนดให้ฟังก์ชันใดๆ f ใช้ SFINAE เพื่อจับคู่ โดยใช้โค้ดที่คุณโพสต์ใน DEMO 2 และส่งพารามิเตอร์จากโค้ดผู้ใช้ (เช่น 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!

สาธิต 3

person Piotr Skotnicki    schedule 24.12.2014
comment
ตัวเลือกที่สองน่าสนใจมาก! คุณคิดว่าเป็นไปได้ที่จะสรุปแนวทางนี้หรือไม่ เพราะเหตุใด ตัวอย่างเช่น กำหนดฟังก์ชันใดๆ f ให้ใช้ SFINAE เพื่อจับคู่ โดยใช้โค้ดที่คุณโพสต์ใน DEMO 2 และส่งพารามิเตอร์จากโค้ดผู้ใช้ (เช่น my_traits<decltype(it_does)>::eval(it_does, parameter, parameter)) - person senseiwa; 24.12.2014
comment
ขอบคุณ @piotr-s มันยอดเยี่ยมมาก! หากฉันไม่เข้าใจผิดโดยดูที่ชุดประกอบการเปิดตัว ก็ไม่มีค่าใช้จ่ายรันไทม์เนื่องจากฉันเห็นเพียง invoke void @_ZNK3seq10does_match9open_fileEi(%"class.seq::does_match"* %it_does, i32 4321) อย่างไรก็ตามฉันเห็นการเรียก call void @llvm.dbg.value(metadata !{%"class.seq::does_match"* %it_does}, i64 0, metadata !13069), !dbg !13059 บ้าง แต่ฉันคิดว่านั่นจะไม่ส่งผลกระทบต่อโค้ดของฉัน ฉันตีความสิ่งนี้ไปในทิศทางที่ถูกต้องหรือไม่? ขอบคุณมาก! - person senseiwa; 26.12.2014