ตรวจสอบว่าคลาสมีฟังก์ชันสมาชิกของลายเซ็นที่กำหนดหรือไม่


135

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

ปัญหาคล้ายกับที่อ้างถึงที่นี่ http://www.gotw.ca/gotw/071.htm แต่ไม่เหมือนกัน: ในรายการหนังสือของซัตเทอร์เขาตอบคำถามที่คลาส C ต้องให้ฟังก์ชันสมาชิกด้วย ลายเซ็นเฉพาะมิฉะนั้นโปรแกรมจะไม่รวบรวม ในปัญหาของฉันฉันต้องทำอะไรบางอย่างถ้าคลาสมีฟังก์ชันนั้นให้ทำ "อย่างอื่น"

ประสบปัญหาที่คล้ายกันโดย boost :: serialization แต่ฉันไม่ชอบวิธีแก้ปัญหาที่พวกเขานำมาใช้: ฟังก์ชันเทมเพลตที่เรียกใช้ฟังก์ชันฟรี (ที่คุณต้องกำหนด) โดยค่าเริ่มต้นด้วยลายเซ็นเฉพาะเว้นแต่คุณจะกำหนดฟังก์ชันสมาชิกเฉพาะ ( ในกรณีของพวกเขา "ทำให้เป็นอนุกรม" ที่ใช้พารามิเตอร์ 2 ตัวในประเภทที่กำหนด) โดยมีลายเซ็นเฉพาะมิฉะนั้นข้อผิดพลาดในการคอมไพล์จะเกิดขึ้น นั่นคือการใช้การทำให้เป็นอนุกรมทั้งแบบล่วงล้ำและไม่ล่วงล้ำ

ฉันไม่ชอบวิธีแก้ปัญหานั้นด้วยเหตุผลสองประการ:

  1. เพื่อไม่ล่วงล้ำคุณต้องแทนที่ฟังก์ชัน "serialize" ทั่วโลกที่อยู่ใน boost :: serialization namespace ดังนั้นคุณจึงมีรหัสลูกค้าของคุณเพื่อเปิดการเพิ่มเนมสเปซและการทำให้เป็นอนุกรมเนมสเปซ!
  2. สแต็กเพื่อแก้ไขปัญหานั้นคือการเรียกใช้ฟังก์ชัน 10 ถึง 12

ฉันต้องการกำหนดพฤติกรรมที่กำหนดเองสำหรับคลาสที่ไม่มีฟังก์ชันสมาชิกนั้นและเอนทิตีของฉันอยู่ภายในเนมสเปซที่แตกต่างกัน (และฉันไม่ต้องการแทนที่ฟังก์ชันส่วนกลางที่กำหนดไว้ในเนมสเปซเดียวในขณะที่ฉันอยู่ในอีกอันหนึ่ง)

คุณช่วยไขปริศนานี้ให้ฉันได้ไหม


1
คำถามที่คล้ายกัน: stackoverflow.com/questions/257288
Johannes Schaub - litb

@ R.MartinhoFernandes คุณกำลังมองหาคำตอบแบบไหน? คำตอบของ Mike Kinghan นี้ค่อนข้างเจาะลึกและใช้งาน C ++ 11
jrok

@ อาร์มาร์ตินโญ่เฟอร์นันเดสบางทีนี่อาจเป็นรุ่นที่ทันสมัยที่คุณกำลังมองหา?
Daniel Frey

คำตอบ:


90

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

template<typename T>
struct HasUsedMemoryMethod
{
    template<typename U, size_t (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
    template<typename U> static int Test(...);
    static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
        // We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
    ReportMemUsage(m, 
        std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}

14
wtf นี่ ??? รหัส c ++ ถูกกฎหมายหรือไม่ ?? คุณสามารถเขียน "template <typename U, size_t (U :: *) () const>" ได้ไหม ?? แต่ ... เป็นทางออกที่ยอดเยี่ยมและใหม่! ฉันขอบคุณฉันจะวิเคราะห์ให้ดีขึ้นในวันพรุ่งนี้กับเพื่อนร่วมงานของฉัน ... เยี่ยมมาก!
ugasoft

2
ตัวอย่างไม่มีคำจำกัดความของ "int_to_type" เห็นได้ชัดว่ามันไม่ได้เพิ่มคำตอบ แต่หมายความว่าผู้คนสามารถเห็นโค้ดของคุณในการทำงานหลังจากตัดและวางอย่างรวดเร็ว
Richard Corden

2
คำจำกัดความง่ายๆของ int_to_type อาจเป็น: 'template <int N> struct int_to_type {};' การใช้งานจำนวนมากจะเก็บค่าพารามิเตอร์ N ไว้ใน enum หรืออื่น ๆ ในค่าคงที่จำนวนเต็มคง (template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
David Rodríguez - dribeas

2
เพียงแค่ใช้ boost :: integral_constant แทน int_to_type
Vadim Ferderer

2
@JohanLundberg เป็นฟังก์ชันของสมาชิกตัวชี้ (ไม่คงที่) ตัวอย่างเช่นsize_t(std::vector::*p)() = &std::vector::size;.
คืนสถานะ Monica

133

นี่คือการใช้งานที่เป็นไปได้โดยอาศัยคุณสมบัติ C ++ 11 ตรวจพบฟังก์ชันได้อย่างถูกต้องแม้ว่าจะได้รับการถ่ายทอดมาก็ตาม (ไม่เหมือนกับการแก้ปัญหาในคำตอบที่ได้รับการยอมรับดังที่ Mike Kinghan สังเกตในคำตอบของเขา )

ฟังก์ชั่นตัวอย่างข้อมูลนี้เรียกว่าserialize:

#include <type_traits>

// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.

template<typename, typename T>
struct has_serialize {
    static_assert(
        std::integral_constant<T, false>::value,
        "Second template parameter needs to be of function type.");
};

// specialization that does the checking

template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
    template<typename T>
    static constexpr auto check(T*)
    -> typename
        std::is_same<
            decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
            Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        >::type;  // attempt to call it and see if the return type is correct

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(0)) type;

public:
    static constexpr bool value = type::value;
};

การใช้งาน:

struct X {
     int serialize(const std::string&) { return 42; } 
};

struct Y : X {};

std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1

ได้ผลหรือไม่ถ้า Y ไม่มีเมธอดที่เรียกว่า "serialize"? ฉันไม่เห็นว่ามันจะส่งคืนค่าเท็จได้อย่างไรหากไม่มีเมธอด "serialize"
Collin

1
@Collin ในกรณีนั้นการแทนที่พารามิเตอร์เทมเพลตจะล้มเหลวสำหรับการตรวจสอบเกินครั้งแรกและถูกทิ้งจากชุดโอเวอร์โหลด มันตกกลับไปที่สองที่ส่งคืน false_type นี่ไม่ใช่ข้อผิดพลาดของคอมไพเลอร์เนื่องจากหลักการ SFINAE
jrok

1
@ elios264 ไม่มีค่ะ คุณสามารถใช้มาโครเพื่อเขียนเทมเพลตสำหรับแต่ละฟังก์ชันที่คุณต้องการตรวจสอบ
jrok

1
เหตุผลเฉพาะใดที่ทำให้อาร์กิวเมนต์สำหรับการตรวจสอบเป็นประเภท T * แทนที่จะเป็น T หรือ T &?
shibumi

1
แต่ถ้าserializeตัวเองยอมรับเทมเพลต มีวิธีทดสอบการserializeดำรงอยู่โดยไม่ต้องพิมพ์ประเภทที่แน่นอนหรือไม่?
Hi-Angel

37

คำตอบที่ได้รับการยอมรับสำหรับคำถามเกี่ยวกับการวิปัสสนาสมาชิก - ฟังก์ชัน compiletime นี้แม้ว่าจะเป็นที่นิยมกันทั่วไป แต่ก็มีอุปสรรค์ที่สามารถสังเกตได้ในโปรแกรมต่อไปนี้:

#include <type_traits>
#include <iostream>
#include <memory>

/*  Here we apply the accepted answer's technique to probe for the
    the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
    template<typename U, E (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::operator*>*);
    template<typename U> static int Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

using namespace std;

/* Here we test the `std::` smart pointer templates, including the
    deprecated `auto_ptr<T>`, to determine in each case whether
    T = (the template instantiated for `int`) provides 
    `int & T::operator*() const` - which all of them in fact do.
*/ 
int main(void)
{
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
    return 0;
}

สร้างขึ้นด้วย GCC 4.6.3, เอาท์พุทโปรแกรม110- แจ้งเราว่า T = std::shared_ptr<int>ไม่ได้int & T::operator*() constให้

หากคุณยังไม่ฉลาดกับ gotcha นี้การดูคำจำกัดความของ std::shared_ptr<T>ในส่วนหัว<memory>จะทำให้เกิดความสว่าง ในการนำไปใช้std::shared_ptr<T>นั้นได้มาจากคลาสพื้นฐานที่สืบทอดoperator*() constมา ดังนั้นการสร้างอินสแตนซ์เทมเพลต SFINAE<U, &U::operator*>ที่ถือว่า "ค้นหา" ตัวดำเนินการสำหรับ U = std::shared_ptr<T>จะไม่เกิดขึ้นเนื่องจากstd::shared_ptr<T>ไม่มีสิทธิ์ operator*()ในตัวเองและการสร้างอินสแตนซ์เทมเพลตจะไม่ "ทำการสืบทอด"

อุปสรรค์นี้ไม่ส่งผลกระทบต่อแนวทาง SFINAE ที่รู้จักกันดีโดยใช้ "The sizeof () Trick" เพื่อตรวจจับว่าTมีฟังก์ชันของสมาชิกอยู่หรือไม่mf(ดูเช่น คำตอบและข้อคิดเห็นนี้) แต่การสร้างที่T::mfมีอยู่มักจะไม่ดีพอ: คุณอาจต้องระบุว่ามีลายเซ็นที่ต้องการ นั่นคือจุดที่เทคนิคในภาพประกอบให้คะแนน ตัวแปรที่ถูกชี้ของลายเซ็นที่ต้องการจะถูกจารึกไว้ในพารามิเตอร์ของประเภทเทมเพลตที่ต้องเป็นที่พอใจ &T::mfเพื่อให้โพรบ SFINAE ประสบความสำเร็จ แต่เทคนิคการสร้างอินสแตนซ์เทมเพลตนี้ให้คำตอบที่ผิดเมื่อT::mfได้รับการสืบทอด

เทคนิค SFINAE ที่ปลอดภัยสำหรับการไตร่ตรองเวลาคอมไพล์ไทม์ของT::mfต้องหลีกเลี่ยงการใช้&T::mfภายในอาร์กิวเมนต์เทมเพลตเพื่อสร้างอินสแตนซ์ประเภทที่ความละเอียดเทมเพลตฟังก์ชัน SFINAE ขึ้นอยู่กับ ความละเอียดฟังก์ชันเทมเพลต SFINAE สามารถขึ้นอยู่กับการประกาศชนิดที่เกี่ยวข้องเท่านั้นที่ใช้เป็นประเภทอาร์กิวเมนต์ของฟังก์ชันโพรบ SFINAE ที่โอเวอร์โหลด

โดยวิธีการตอบคำถามที่ปฏิบัติตามข้อ จำกัด นี้ผมจะแสดงให้เห็นถึงการตรวจหา compiletime E T::operator*() constสำหรับพลและT Eรูปแบบเดียวกันนี้จะนำไปใช้ กับการตรวจสอบลายเซ็นของสมาชิกอื่น ๆโดยอนุโลม

#include <type_traits>

/*! The template `has_const_reference_op<T,E>` exports a
    boolean constant `value that is true iff `T` provides
    `E T::operator*() const`
*/ 
template< typename T, typename E>
struct has_const_reference_op
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A>
    static std::true_type test(E (A::*)() const) {
        return std::true_type();
    }

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

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

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

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

ในโซลูชันนี้ฟังก์ชันโพรบ SFINAE ที่โอเวอร์โหลดtest()คือ "เรียกใช้ซ้ำ" (แน่นอนว่ามันไม่ได้ถูกเรียกใช้เลย แต่มีเพียงประเภทการส่งคืนของการเรียกใช้สมมุติที่แก้ไขโดยคอมไพเลอร์)

เราจำเป็นต้องตรวจสอบข้อมูลอย่างน้อยหนึ่งจุดและไม่เกินสองจุด:

  • ไม่T::operator*()อยู่ที่ทั้งหมดหรือไม่ ถ้าไม่เราเสร็จแล้ว
  • ระบุว่าT::operator*()มีอยู่เป็นลายเซ็น E T::operator*() constหรือไม่?

test(0,0)เราได้คำตอบโดยการประเมินผลตอบแทนประเภทของสายเดียวที่จะ ทำได้โดย:

    typedef decltype(test<T>(0,0)) type;

การโทรนี้อาจได้รับการแก้ไขเป็น/* SFINAE operator-exists :) */โอเวอร์โหลดtest()หรืออาจแก้ไขการ/* SFINAE game over :( */โอเวอร์โหลด มันไม่สามารถแก้ไขการ/* SFINAE operator-has-correct-sig :) */โอเวอร์โหลดได้เพราะสิ่งนั้นคาดหวังเพียงอาร์กิวเมนต์เดียวและเรากำลังผ่านสองข้อ

ทำไมเราถึงผ่านสอง? /* SFINAE operator-has-correct-sig :) */เพียงเพื่อบังคับให้ความละเอียดในการยกเว้น อาร์กิวเมนต์ที่สองไม่มีความหมายอื่นใด

สายนี้ไปtest(0,0)จะแก้ไป/* SFINAE operator-exists :) */เพียงในกรณีที่อาร์กิวเมนต์แรก 0 satifies ชนิดพารามิเตอร์แรกของเกินพิกัดที่ซึ่งเป็นที่มีdecltype(&A::operator*) A = T0 จะตอบสนองประเภทนั้นในกรณีที่T::operator*มีอยู่

สมมติว่าคอมไพเลอร์พูดว่าใช่ จากนั้นจะดำเนินการตาม /* SFINAE operator-exists :) */และจำเป็นต้องกำหนดประเภทการส่งคืนของการเรียกใช้ฟังก์ชันซึ่งในกรณีนี้คือdecltype(test(&A::operator*))- ประเภทการส่งคืนของการเรียกใช้test()อื่น

คราวนี้เราส่งผ่านข้อโต้แย้งเพียงข้อเดียว&A::operator*ซึ่งตอนนี้เรารู้แล้วว่ามีอยู่หรือเราจะไม่อยู่ที่นี่ การเรียกร้องให้test(&A::operator*)อาจมีมติอย่างใดอย่างหนึ่งหรืออีกครั้งเพื่ออาจจะมีมติให้/* SFINAE operator-has-correct-sig :) */ /* SFINAE game over :( */โทรจะตรงกับ /* SFINAE operator-has-correct-sig :) */ในกรณีที่&A::operator*น่าพอใจชนิดพารามิเตอร์เดียวที่เกินนั้นซึ่งเป็นด้วยE (A::*)() constA = T

คอมไพเลอร์จะพูดว่า Yes ที่นี่หากT::operator*มีลายเซ็นที่ต้องการจากนั้นต้องประเมินประเภทการส่งคืนของโอเวอร์โหลดอีกครั้ง ไม่มีรายละเอียดเพิ่มเติม "recursions" ตอนนี้: std::true_typeมันเป็น

ถ้าคอมไพเลอร์ไม่ได้เลือก/* SFINAE operator-exists :) */สำหรับการโทรtest(0,0)หรือไม่เลือก/* SFINAE operator-has-correct-sig :) */ สำหรับการโทรtest(&A::operator*)แล้วว่าในกรณีใดมันจะไปด้วย และผลตอบแทนประเภทสุดท้ายคือ/* SFINAE game over :( */std::false_type

นี่คือโปรแกรมทดสอบที่แสดงเทมเพลตที่ให้คำตอบที่คาดหวังในกรณีตัวอย่างต่างๆ (GCC 4.6.3 อีกครั้ง)

// To test
struct empty{};

// To test 
struct int_ref
{
    int & operator*() const {
        return *_pint;
    }
    int & foo() const {
        return *_pint;
    }
    int * _pint;
};

// To test 
struct sub_int_ref : int_ref{};

// To test 
template<typename E>
struct ee_ref
{
    E & operator*() {
        return *_pe;
    }
    E & foo() const {
        return *_pe;
    }
    E * _pe;
};

// To test 
struct sub_ee_ref : ee_ref<char>{};

using namespace std;

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    cout << "Expect Yes" << endl;
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value;
    cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
    cout << has_const_reference_op<std::vector<int>::const_iterator,
            int const &>::value;
    cout << has_const_reference_op<int_ref,int &>::value;
    cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
    cout << "Expect No" << endl;
    cout << has_const_reference_op<int *,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,char &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int>::value;
    cout << has_const_reference_op<unique_ptr<long>,int &>::value;
    cout << has_const_reference_op<int,int>::value;
    cout << has_const_reference_op<std::vector<int>,int &>::value;
    cout << has_const_reference_op<ee_ref<int>,int &>::value;
    cout << has_const_reference_op<sub_ee_ref,int &>::value;
    cout << has_const_reference_op<empty,int &>::value  << endl;
    return 0;
}

มีข้อบกพร่องใหม่ในความคิดนี้หรือไม่? มันสามารถทำให้ทั่วไปมากขึ้นโดยไม่ต้องล้มเหลวจากอุปสรรค์อีกครั้งได้หรือไม่?


16

นี่คือตัวอย่างการใช้งานบางส่วน: * ความกล้าสำหรับทั้งหมดนี้อยู่ไกลออกไป

ตรวจหาสมาชิกxในชั้นเรียนที่กำหนด อาจเป็น var, func, class, union หรือ enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

ตรวจสอบฟังก์ชันสมาชิกvoid x():

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

ตรวจสอบตัวแปรสมาชิกx:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

ตรวจสอบคลาสสมาชิกx:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

ตรวจสอบสหภาพสมาชิกx:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

ตรวจสอบสมาชิก enum x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

ตรวจสอบฟังก์ชันสมาชิกxโดยไม่คำนึงถึงลายเซ็น:

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

หรือ

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

รายละเอียดและหลัก:

/*
    - Multiple inheritance forces ambiguity of member names.
    - SFINAE is used to make aliases to member names.
    - Expression SFINAE is used in just one generic has_member that can accept
      any alias we pass it.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

template<typename A, typename = void>
struct got_type : std::false_type {};

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(0)) == 1)
        , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
    );

    static bool const value = sizeof(f<Alias>(0)) == 2;
};

มาโคร (El Diablo!):

CREATE_MEMBER_CHECK:

//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member)                                         \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct Alias_##member;                                                      \
                                                                            \
template<typename T>                                                        \
struct Alias_##member <                                                     \
    T, std::integral_constant<bool, got_type<decltype(&T::member)>::value>  \
> { static const decltype(&T::member) value; };                             \
                                                                            \
struct AmbiguitySeed_##member { char member; };                             \
                                                                            \
template<typename T>                                                        \
struct has_member_##member {                                                \
    static const bool value                                                 \
        = has_member<                                                       \
            Alias_##member<ambiguate<T, AmbiguitySeed_##member>>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_SIG_CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_class_##class_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_class_##class_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_class<                                    \
            typename got_type<typename T::class_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_UNION_CHECK:

//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_union_##union_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_union_##union_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_union<                                    \
            typename got_type<typename T::union_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_ENUM_CHECK:

//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name)                 \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_enum_##enum_name : std::false_type {};    \
                                                            \
template<typename T>                                        \
struct has_member_enum_##enum_name<                         \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_enum<                                     \
            typename got_type<typename T::enum_name>::type  \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::value    \
    ;                                           \
}

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)

1
มันยอดเยี่ยมมาก จะเป็นการดีที่จะใส่ไว้ในไลบรารีไฟล์ส่วนหัวเดียว
Allan

12

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

template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
  A a;
  B b;
  bla(b);
  bla(a);
}

4
Thaks! คล้ายกับโซลูชันที่เสนอโดย yrp ฉันไม่รู้ว่าเทมเพลตนั้นสามารถเป็นเทมเพลตผ่านฟังก์ชันของสมาชิกได้ นั่นเป็นคุณสมบัติใหม่ที่ฉันได้เรียนรู้ในวันนี้! ... และบทเรียนใหม่: "never say you are expert on c ++" :)
ugasoft

7

นี่คือคำตอบของ Mike Kinghan ที่ง่ายกว่า วิธีนี้จะตรวจหาวิธีการที่สืบทอดมา นอกจากนี้ยังตรวจสอบลายเซ็นที่แน่นอน (ไม่เหมือนกับวิธีการของ jrok ที่อนุญาตให้มีการแปลงอาร์กิวเมนต์)

template <class C>
class HasGreetMethod
{
    template <class T>
    static std::true_type testSignature(void (T::*)(const char*) const);

    template <class T>
    static decltype(testSignature(&T::greet)) test(std::nullptr_t);

    template <class T>
    static std::false_type test(...);

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");

ตัวอย่างที่รันได้


นี่เป็นสิ่งที่ดี แต่จะไม่ทำงานหากฟังก์ชันไม่มีอาร์กิวเมนต์
Triskeldeian

มันทำงานได้ดี ฉันไม่มีปัญหาในการนำเคล็ดลับนี้ไปใช้กับฟังก์ชันของสมาชิกโดยไม่มีข้อโต้แย้ง
JohnB

สิ่งนี้ใช้ได้ดีสำหรับฉันที่มีอาร์กิวเมนต์หลายวิธีและไม่มีเมธอดรวมถึงโอเวอร์โหลดและรวมถึงการสืบทอดและการใช้usingเพื่อนำโอเวอร์โหลดจากคลาสพื้นฐาน มันใช้ได้กับฉันใน MSVC 2015 และ Clang-CL อย่างไรก็ตามมันใช้ไม่ได้กับ MSVC 2012
steveire

5

คุณสามารถใช้std :: is_member_function_pointer

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;

16
จะไม่&A::fooเป็นข้อผิดพลาดในการคอมไพล์ถ้าไม่มีfooเลยA? ฉันอ่านคำถามเดิมว่าควรจะทำงานกับคลาสอินพุตใดก็ได้ไม่ใช่แค่คำถามที่มีชื่อสมาชิกบางประเภทfooเท่านั้น
Jeff Walden

5

มาพร้อมกับปัญหาเดียวกันกับตัวเองและพบว่าแนวทางแก้ไขที่เสนอในที่นี้น่าสนใจมาก ... แต่มีข้อกำหนดสำหรับวิธีแก้ปัญหาที่:

  1. ตรวจจับฟังก์ชันที่สืบทอดมาเช่นกัน
  2. เข้ากันได้กับคอมไพเลอร์ที่ไม่ใช่ C ++ 11 (ดังนั้นจึงไม่มีประเภทการปฏิเสธ)

พบอีกด้ายเสนอบางอย่างเช่นนี้อยู่บนพื้นฐานของการอภิปราย BOOST นี่คือการสรุปทั่วไปของโซลูชันที่นำเสนอเป็นการประกาศมาโครสองรายการสำหรับคลาสลักษณะตามโมเดลของคลาสboost :: has_ ​​*

#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>

/// Has constant function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)

/// Has non-const function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)

// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...)  \
    template                                                                  \
    <   typename Type,                                                        \
        bool is_class = boost::is_class<Type>::value                          \
    >                                                                         \
    class has_func_ ## func_name;                                             \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,false>                                  \
    {public:                                                                  \
        BOOST_STATIC_CONSTANT( bool, value = false );                         \
        typedef boost::false_type type;                                       \
    };                                                                        \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,true>                                   \
    {   struct yes { char _foo; };                                            \
        struct no { yes _foo[2]; };                                           \
        struct Fallback                                                       \
        {   func_ret_type func_name( __VA_ARGS__ )                            \
                UTILITY_OPTIONAL(func_const,const) {}                         \
        };                                                                    \
        struct Derived : public Type, public Fallback {};                     \
        template <typename T, T t>  class Helper{};                           \
        template <typename U>                                                 \
        static no deduce(U*, Helper                                           \
            <   func_ret_type (Fallback::*)( __VA_ARGS__ )                    \
                    UTILITY_OPTIONAL(func_const,const),                       \
                &U::func_name                                                 \
            >* = 0                                                            \
        );                                                                    \
        static yes deduce(...);                                               \
    public:                                                                   \
        BOOST_STATIC_CONSTANT(                                                \
            bool,                                                             \
            value = sizeof(yes)                                               \
                == sizeof( deduce( static_cast<Derived*>(0) ) )               \
        );                                                                    \
        typedef ::boost::integral_constant<bool,value> type;                  \
        BOOST_STATIC_CONSTANT(bool, is_const = func_const);                   \
        typedef func_ret_type return_type;                                    \
        typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;                \
    }

// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__

มาโครเหล่านี้ขยายเป็นคลาสลักษณะด้วยต้นแบบต่อไปนี้:

template<class T>
class has_func_[func_name]
{
public:
    /// Function definition result value
    /** Tells if the tested function is defined for type T or not.
    */
    static const bool value = true | false;

    /// Function definition result type
    /** Type representing the value attribute usable in
        http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
    */
    typedef boost::integral_constant<bool,value> type;

    /// Tested function constness indicator
    /** Indicates if the tested function is const or not.
        This value is not deduced, it is forced depending
        on the user call to one of the traits generators.
    */
    static const bool is_const = true | false;

    /// Tested function return type
    /** Indicates the return type of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef func_ret_type return_type;

    /// Tested function arguments types
    /** Indicates the arguments types of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};

แล้วการใช้งานทั่วไปที่เราสามารถทำได้คืออะไร?

// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
    // Next line will declare the traits class
    // to detect the member function void foo(int,int) const
    DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}

// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>

// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   _this_.foo(a,b);
}

// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   default_foo(_this_,a,b);
}

// Let us declare test types
struct empty
{
};
struct direct_foo
{
    void foo(int,int);
};
struct direct_const_foo
{
    void foo(int,int) const;
};
struct inherited_const_foo :
    public direct_const_foo
{
};

// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
    int a;
    foo_bar(a); // calls default_foo

    empty b;
    foo_bar(b); // calls default_foo

    direct_foo c;
    foo_bar(c); // calls default_foo (member function is not const)

    direct_const_foo d;
    foo_bar(d); // calls d.foo (member function is const)

    inherited_const_foo e;
    foo_bar(e); // calls e.foo (inherited member function)
}

5

เพื่อให้บรรลุเป้าหมายนี้เราจะต้องใช้:

  1. เทมเพลตฟังก์ชันทำงานมากเกินไปด้วยประเภทการส่งคืนที่แตกต่างกันตามว่ามีวิธีการหรือไม่
  2. เพื่อให้เป็นไปตาม meta-conditionals ในtype_traitsส่วนหัวเราจะต้องส่งคืนtrue_typeหรือfalse_typeจากการโอเวอร์โหลดของเรา
  3. ประกาศการtrue_typeโอเวอร์โหลดที่คาดว่าจะมีintและfalse_typeโอเวอร์โหลดที่คาดว่าพารามิเตอร์ Variadic จะใช้ประโยชน์: "ลำดับความสำคัญต่ำสุดของการแปลงจุดไข่ปลาในความละเอียดโอเวอร์โหลด"
  4. ในการกำหนดคุณสมบัติแม่แบบสำหรับtrue_typeฟังก์ชันที่เราจะใช้declvalและdecltypeช่วยให้เราตรวจพบฟังก์ชันโดยไม่ขึ้นกับความแตกต่างของประเภทการส่งคืนหรือการโอเวอร์โหลดระหว่างวิธีการ

คุณสามารถดูตัวอย่างจริงของเรื่องนี้ที่นี่ แต่ฉันจะอธิบายไว้ด้านล่าง:

ฉันต้องการตรวจสอบการมีอยู่ของฟังก์ชั่นที่ตั้งชื่อtestซึ่งใช้ประเภทที่เปลี่ยนแปลงได้จากintนั้นฉันต้องประกาศสองฟังก์ชันนี้:

template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
  • decltype(hasTest<a>(0))::valueคือtrue(โปรดทราบว่าไม่จำเป็นต้องสร้างฟังก์ชันพิเศษเพื่อจัดการกับการvoid a::test()โอเวอร์โหลดซึ่งvoid a::test(int)เป็นที่ยอมรับ)
  • decltype(hasTest<b>(0))::valueคือtrue(เนื่องจากintสามารถแปลงdouble int b::test(double)เป็นได้รับการยอมรับโดยไม่ขึ้นกับประเภทผลตอบแทน)
  • decltype(hasTest<c>(0))::valueคือfalse( cไม่มีเมธอดชื่อtestที่ยอมรับประเภทที่เปลี่ยนแปลงได้จากintนั้นจึงไม่ยอมรับ)

โซลูชันนี้มีข้อเสีย 2 ประการ:

  1. ต้องมีการประกาศต่อวิธีการของคู่ของฟังก์ชัน
  2. สร้างเนมสเปซมลพิษโดยเฉพาะถ้าเราต้องการทดสอบชื่อที่คล้ายกันเช่นเราจะตั้งชื่อฟังก์ชันที่ต้องการทดสอบtest()เมธอดอะไร

ดังนั้นจึงเป็นเรื่องสำคัญที่จะต้องประกาศฟังก์ชันเหล่านี้ในเนมสเปซแบบละเอียดหรือหากต้องการใช้กับคลาสเท่านั้นฟังก์ชันเหล่านี้ควรได้รับการประกาศแบบส่วนตัวโดยคลาสนั้น ด้วยเหตุนี้ฉันจึงเขียนมาโครเพื่อช่วยคุณสรุปข้อมูลนี้:

#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
                              template <typename T> static false_type __ ## DEFINE(...); \
                              template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));

คุณสามารถใช้สิ่งนี้:

namespace details {
    FOO(test(declval<int>()), test_int)
    FOO(test(), test_void)
}

เรียกในภายหลังdetails::test_int<a>::valueหรือdetails::test_void<a>::valueจะให้ผลtrueหรือfalseเพื่อวัตถุประสงค์ของรหัสอินไลน์หรือการเขียนโปรแกรมเมตา


3

เพื่อที่จะไม่ล่วงล้ำคุณยังสามารถใส่serializeใน namespace ของชั้นที่มีการต่อเนื่องหรือของชั้นเก็บขอบคุณค้นหานิก ดูNamespaces สำหรับการแทนที่ฟังก์ชันฟรีสำหรับรายละเอียดเพิ่มเติม :-)

การเปิดเนมสเปซใด ๆ เพื่อใช้ฟังก์ชันฟรีนั้นผิดเพียง (เช่นคุณไม่ควรเปิดเนมสเปซstdเพื่อใช้swapกับประเภทของคุณเอง แต่ควรใช้การค้นหา Koenig แทน)


3

ดูเหมือนว่าคุณต้องการสำนวนการตรวจจับ คำตอบข้างต้นเป็นรูปแบบต่างๆที่ใช้ได้กับ C ++ 11 หรือ C ++ 14

std::experimentalห้องสมุดมีคุณสมบัติที่ทำหลักนี้ ทำการปรับปรุงตัวอย่างจากด้านบนอาจเป็น:

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

หากคุณไม่สามารถใช้ std :: experiment เวอร์ชันพื้นฐานสามารถทำได้ดังนี้:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

เนื่องจาก has_serialize_t เป็น std :: true_type หรือ std :: false_type จึงสามารถใช้ผ่านสำนวน SFINAE ทั่วไป:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

หรือโดยใช้การจัดส่งที่มีความละเอียดเกินพิกัด:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}

2

ตกลง. ลองครั้งที่สอง ไม่เป็นไรถ้าคุณไม่ชอบอันนี้ฉันกำลังมองหาไอเดียเพิ่มเติม

บทความของ Herb Sutter พูดถึงลักษณะนิสัย ดังนั้นคุณสามารถมีคลาสลักษณะที่มีการสร้างอินสแตนซ์เริ่มต้นมีพฤติกรรมทางเลือกและสำหรับแต่ละคลาสที่มีฟังก์ชันสมาชิกของคุณคลาสลักษณะพิเศษจะเรียกใช้ฟังก์ชันสมาชิก ฉันเชื่อว่าบทความของ Herb กล่าวถึงเทคนิคในการทำเช่นนี้เพื่อไม่ให้มีการคัดลอกและวางจำนวนมาก

อย่างที่ฉันพูดไปบางทีคุณอาจไม่ต้องการงานพิเศษที่เกี่ยวข้องกับคลาส "การแท็ก" ที่ใช้สมาชิกนั้น ในกรณีนี้ฉันกำลังดูวิธีแก้ปัญหาที่สาม ....


เอ๊ะ ... ฉันได้วิเคราะห์โซลูชันนี้แล้ว ... ฉันคิดว่ามันแพงเกินไปสำหรับผู้ใช้เฟรมเวิร์คของฉัน (ตกลงฉันยอมรับว่าฉันกำลังพัฒนาเฟรมเวิร์กการสตรีมและฉันกำลังเลือกระหว่างการขยาย iostream หรือการเขียนสิ่งที่ง่ายขึ้น)
ugasoft

วิธีที่สามของฉันคือการใช้ SFINAE เนื่องจากคำตอบของ yrp ได้กล่าวถึงมันแล้วฉันจะไม่เข้าไป (เพราะฉันยังคงค้นคว้าเกี่ยวกับเรื่องนี้: ฉันรู้ความคิด แต่ปีศาจอยู่ในรายละเอียด) เว้นแต่วิธีแก้ปัญหาของเขาจะไม่ได้ผลสำหรับคุณในตอนท้าย . :-)
Chris Jester-Young

1

หากไม่มีการสนับสนุน C ++ 11 ( decltype) สิ่งนี้อาจใช้งานได้:

SSCCE

#include <iostream>
using namespace std;

struct A { void foo(void); };
struct Aa: public A { };
struct B { };

struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };

template<typename T>
struct FooFinder {
    typedef char true_type[1];
    typedef char false_type[2];

    template<int>
    struct TypeSink;

    template<class U>
    static true_type &match(U);

    template<class U>
    static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);

    template<class U>
    static false_type &test(...);

    enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};

int main() {
    cout << FooFinder<A>::value << endl;
    cout << FooFinder<Aa>::value << endl;
    cout << FooFinder<B>::value << endl;

    cout << FooFinder<retA>::value << endl;
    cout << FooFinder<argA>::value << endl;
    cout << FooFinder<constA>::value << endl;
    cout << FooFinder<varA>::value << endl;
}

หวังว่าจะได้ผล

A, AaและBมี clases ในคำถามที่Aaเป็นคนพิเศษที่สืบทอดสมาชิกที่เรากำลังมองหา

ในและจะเปลี่ยนสำหรับผู้สื่อข่าว C ++ 11 ชั้นเรียน นอกจากนี้เพื่อความเข้าใจเกี่ยวกับการเขียนโปรแกรมเมตาเทมเพลตพวกเขาเปิดเผยพื้นฐานของ SFINAE-sizeof-trickFooFindertrue_typefalse_type

TypeSinkเป็น struct แม่แบบที่ถูกนำมาใช้ในภายหลังเพื่อจมผลหนึ่งของsizeofผู้ประกอบการเป็น instantiation แม่แบบในรูปแบบประเภท

matchฟังก์ชั่นเป็นอีกหนึ่งชนิด SFINAE ของแม่แบบที่ถูกทิ้งไว้โดยไม่มีคู่ทั่วไป ดังนั้นจึงสามารถสร้างอินสแตนซ์ได้ก็ต่อเมื่อประเภทของอาร์กิวเมนต์ตรงกับประเภทที่ระบุไว้โดยเฉพาะ

ทั้งสองtestฟังก์ชันพร้อมกับการประกาศ enum ในที่สุดก็สร้างรูปแบบ SFINAE กลาง มีแบบทั่วไปที่ใช้จุดไข่ปลาที่ส่งกลับfalse_typeและคู่กับอาร์กิวเมนต์ที่เฉพาะเจาะจงมากขึ้นเพื่อให้มีความสำคัญ

เพื่อให้สามารถยกตัวอย่างtestฟังก์ชั่นที่มีการโต้แย้งของแม่แบบTที่matchฟังก์ชั่นจะต้องมีการยกตัวอย่างเช่นประเภทกลับมาจะต้องมีการยกตัวอย่างTypeSinkการโต้แย้ง ข้อแม้คือการ&U::fooรวมอาร์กิวเมนต์ของฟังก์ชันจะไม่ถูกอ้างถึงจากความเชี่ยวชาญด้านอาร์กิวเมนต์เทมเพลตดังนั้นการค้นหาสมาชิกที่สืบทอดมาจึงยังคงเกิดขึ้น


1

หากคุณใช้ความโง่เขลาของ facebook พวกเขาจะอยู่นอกกรอบมาโครเพื่อช่วยคุณ:

#include <folly/Traits.h>
namespace {
  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace

void some_func() {
  cout << "Does class Foo have a member int test() const? "
    << boolalpha << has_test_traits<Foo, int() const>::value;
}

แม้ว่ารายละเอียดการใช้งานจะเหมือนกันกับคำตอบก่อนหน้า แต่การใช้ไลบรารีนั้นง่ายกว่า


0

ฉันมีความต้องการคล้าย ๆ กันและเจอกับสิ่งนี้ มีโซลูชันที่น่าสนใจ / มีประสิทธิภาพมากมายที่นำเสนอที่นี่แม้ว่าจะยาวไปหน่อยสำหรับความต้องการเฉพาะ: ตรวจสอบว่าคลาสมีฟังก์ชันสมาชิกพร้อมลายเซ็นที่แม่นยำหรือไม่ ดังนั้นฉันจึงทำการอ่าน / ทดสอบและหาเวอร์ชันของฉันที่น่าสนใจ ตรวจพบ:

  • ฟังก์ชันสมาชิกแบบคงที่
  • ฟังก์ชันสมาชิกไม่คงที่
  • ฟังก์ชันสมาชิกที่ไม่คงที่ const

ด้วยลายเซ็นที่แม่นยำ เนื่องจากฉันไม่จำเป็นต้องจับลายเซ็นใด ๆ (ซึ่งต้องการวิธีแก้ปัญหาที่ซับซ้อนกว่านี้) อันนี้เหมาะกับฉัน มันใช้เป็นพื้นenable_if_t

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

เอาท์พุต:

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1

0

จากคำตอบของjrokฉันได้หลีกเลี่ยงการใช้คลาสเทมเพลตและ / หรือฟังก์ชันที่ซ้อนกัน

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

เราสามารถใช้มาโครด้านบนดังต่อไปนี้:

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

ข้อเสนอแนะยินดีต้อนรับ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.