تحقق مما إذا كان الفصل يحتوي على وظيفة عضو لتوقيع معين

StackOverflow https://stackoverflow.com/questions/87372

  •  01-07-2019
  •  | 
  •  

سؤال

أطلب خدعة قالبية لاكتشاف ما إذا كان الفصل لديه وظيفة عضو محددة لتوقيع معين.

المشكلة مشابهة لتلك المذكورة هناhttp://www.gotw.ca/gotw/071.htmولكن ليس نفس الشيء:في فقرة كتاب سوتر أجاب على السؤال القائل بأن الفئة C يجب أن توفر وظيفة عضو بتوقيع معين، وإلا فلن يتم تجميع البرنامج.في مشكلتي، أحتاج إلى القيام بشيء ما إذا كان الفصل لديه هذه الوظيفة، وإلا افعل "شيئًا آخر".

تمت مواجهة مشكلة مماثلة من خلال Boost::serialization ولكني لا أحب الحل الذي اعتمدوه:وظيفة قالب تستدعي بشكل افتراضي وظيفة مجانية (يجب عليك تحديدها) بتوقيع معين ما لم تحدد وظيفة عضو معينة (في حالتها "التسلسل" الذي يأخذ معلمتين من نوع معين) بتوقيع معين، وإلا سيحدث خطأ في الترجمة.وذلك لتنفيذ كل من التسلسل التدخلي وغير التدخلي.

لا أحب هذا الحل لسببين:

  1. لكي تكون غير متطفل، يجب عليك تجاوز وظيفة "التسلسل" العالمية الموجودة في مساحة الاسم Boost::Serialization، بحيث يكون لديك في رمز العميل الخاص بك لفتح تعزيز مساحة الاسم وتسلسل مساحة الاسم!
  2. المكدس لحل تلك الفوضى كانت 10 إلى 12 دعوة وظيفة.

أحتاج إلى تحديد سلوك مخصص للفئات التي لا تحتوي على وظيفة العضو، والكيانات الخاصة بي موجودة داخل مساحات أسماء مختلفة (ولا أريد تجاوز وظيفة عامة محددة في مساحة اسم واحدة بينما أكون في مساحة أخرى)

هل يمكن أن تعطيني تلميحا لحل هذا اللغز؟

هل كانت مفيدة؟

المحلول

لست متأكدًا مما إذا كنت أفهمك بشكل صحيح، ولكن يمكنك استغلال SFINAE لاكتشاف وجود الوظيفة في وقت الترجمة.مثال من الكود الخاص بي (اختبارات إذا كان الفصل يحتوي على وظيفة العضو size_tused_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>());
}

نصائح أخرى

فيما يلي تطبيق محتمل يعتمد على ميزات C++ 11.فهو يكتشف الوظيفة بشكل صحيح حتى لو كانت موروثة (على عكس الحل الموجود في الإجابة المقبولة، كما لاحظ مايك كينغان في إجابته).

تسمى الوظيفة التي يختبرها هذا المقتطف 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

الإجابة المقبولة على هذا السؤال عن التأمل في وظائف الأعضاء في وقت ، على الرغم من أنه يتمتع بشعبية كبيرة ، إلا أنه يحتوي على عقبة يمكن ملاحظتها في البرنامج التالي:

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

بنيت مع دول مجلس التعاون الخليجي 4.6.3، ومخرجات البرنامج 110 - إعلامنا بذلكT = std::shared_ptr<int> يفعل لا يمد int & T::operator*() const.

إذا لم تكن حكيمًا بالفعل في التعامل مع هذا الأمر، فقم بإلقاء نظرة على تعريف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 المعروف ، باستخدام "SizeOF () خدعة" ، لاكتشاف فقط سواء T لديه بعض وظائف الأعضاء mf (انظر على سبيل المثالهذه الإجابة والتعليقات).لكن تأسيس ذلك T::mf موجود في كثير من الأحيان (عادة؟) ليس جيدًا بما فيه الكفاية:قد تحتاج أيضًا إلى إثبات أن لديها توقيعًا مطلوبًا.هذا هو المكان الذي درجات التقنية المصور.تم إدراج البديل المثير للتوقيع المطلوب في معلمة من نوع القالب الذي يجب أن يكون راضيا&T::mf لنجاح مسبار SFINAE.لكن تقنية إنشاء إنشاء القالب هذه تعطي الإجابة الخاطئة عندما T::mf موروث.

تقنية SFINAE الآمنة لاستبطان وقت التجميع T::mf يجب تجنب استخدام &T::mf ضمن حجة قالب لتشكيل نوع يعتمد عليه دقة قالب وظيفة Sfinae.بدلاً من ذلك ، يمكن أن يعتمد دقة وظيفة قالب SFINAE فقط على إعلانات النوع ذات الصلة بالضبط المستخدمة كنوع وسيطة من وظيفة مسبار SFINAE المفرطة.

عن طريق إجابة على السؤال الذي يلتزم بهذا القيد ، سأوضح لاكتشاف وقت 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 نوع المعلمة الأولى من هذا الحمل الزائد ، وهو decltype(&A::operator*), ، مع A = T.0 سوف يرضي هذا النوع فقط في حالة 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::*)() const، مع A = T.

سيقول المترجم نعم هنا إذا T::operator* لديه هذا التوقيع المطلوب ، ومن ثم يتعين على تقييم نوع العودة من الحمل الزائد.لا مزيد من "التكرارات" الآن:إنها 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;
}

هل هناك عيوب جديدة في هذه الفكرة؟هل يمكن أن يكون أكثر عاما دون أن تسقط مرة أخرى خطأ من العقبة التي يتجنبها؟

فيما يلي بعض مقتطفات الاستخدام:*الشجاعة في كل هذا تقع في الأسفل

التحقق من وجود عضو 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;

التحقق من وجود تعداد الأعضاء 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;
};

وحدات الماكرو (إل ديابلو!):

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)

يجب أن يكون هذا كافيًا، إذا كنت تعرف اسم وظيفة العضو التي تتوقعها.(في هذه الحالة، تفشل الدالة 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);
}

فيما يلي نظرة أبسط على إجابة مايك كينغهان.سيؤدي هذا إلى اكتشاف الأساليب الموروثة.كما سيتم التحقق من بالضبط التوقيع (على عكس نهج 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, "");

قابل للتشغيل مثال

يمكنك استخدام std::is_member_function_pointer

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

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

واجهت نفس المشكلة بنفسي، ووجدت الحلول المقترحة هنا مثيرة جدًا للاهتمام...ولكن كان لديه شرط الحل الذي:

  1. يكتشف الوظائف الموروثة أيضًا؛
  2. متوافق مع المترجمين غير الجاهزين لـ C++ 11 (لذلك لا يوجد نوع decltype)

وجدت اخر خيط اقتراح شيء من هذا القبيل، على أساس أ تعزيز المناقشة.هنا تعميم الحل المقترح كإعلان ماكروين لفئة السمات، باتباع نموذج دفعة::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)
}

لتحقيق ذلك سنحتاج إلى استخدام:

  1. التحميل الزائد لقالب الوظيفة بأنواع إرجاع مختلفة وفقًا لما إذا كانت الطريقة متاحة أم لا
  2. تمشيا مع الشروط الفوقية في 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 لذلك لا يقبل)

هذا الحل له عيبان:

  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 لأغراض التعليمات البرمجية المضمنة أو البرمجة التعريفية.

لتكون غير تدخلية، يمكنك أيضا وضع serialize في مساحة اسم الفئة التي يتم إجراء تسلسل لها، أو فئة الأرشيف، وذلك بفضل بحث عن كونيج.يرى مساحات الأسماء لتجاوزات الوظائف المجانية لمزيد من التفاصيل.:-)

يعد فتح أي مساحة اسم محددة لتنفيذ وظيفة مجانية أمرًا خاطئًا ببساطة.(على سبيل المثال، ليس من المفترض أن تفتح مساحة الاسم std لتنفيذ swap للأنواع الخاصة بك، ولكن يجب استخدام بحث Koenig بدلاً من ذلك.)

تمام.محاولة ثانية.لا بأس إذا لم يعجبك هذا أيضًا، فأنا أبحث عن المزيد من الأفكار.

تتحدث مقالة هيرب سوتر عن السمات.لذلك يمكن أن يكون لديك فئة سمات يكون إنشاء مثيلها الافتراضي له السلوك الاحتياطي، ولكل فئة توجد بها وظيفة العضو الخاصة بك، فإن فئة السمات تكون متخصصة لاستدعاء وظيفة العضو.أعتقد أن مقالة هيرب تشير إلى تقنية للقيام بذلك بحيث لا تتضمن الكثير من النسخ واللصق.

كما قلت، ربما لا ترغب في العمل الإضافي المتضمن في فئات "وضع العلامات" التي تنفذ هذا العضو.وفي هذه الحالة أنا أبحث عن حل ثالث....

بدون دعم 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 هي الفئات المعنية ، Aa كونه العضو المميز الذي يرث العضو الذي نبحث عنه.

في ال FooFinder ال true_type و false_type هي البدائل لفئات C++ 11 المقابلة.أيضًا لفهم البرمجة التعريفية للقالب، فقد كشفوا عن أساس خدعة SFINAE-sizeof.

ال TypeSink عبارة عن بنية قالب يتم استخدامها لاحقًا لإغراق النتيجة المتكاملة لـ sizeof عامل التشغيل في إنشاء مثيل للقالب لتشكيل نوع.

ال match الوظيفة هي نوع آخر من قوالب SFINAE التي يتم تركها بدون نظير عام.ومن ثم لا يمكن إنشاء مثيل له إلا إذا كان نوع وسيطته يتطابق مع النوع المخصص له.

كلا ال test تشكل الوظائف مع إعلان التعداد أخيرًا نمط SFINAE المركزي.هناك طريقة عامة تستخدم علامة الحذف التي تُرجع ملف false_type ونظير له حجج أكثر تحديدًا لأخذ الأسبقية.

لتكون قادرة على إنشاء مثيل test وظيفة مع وسيطة قالب T, ، ال match يجب إنشاء مثيل للدالة، حيث أن نوع الإرجاع الخاص بها مطلوب لإنشاء مثيل لها TypeSink دعوى.التحذير هو ذلك &U::foo, ، يتم تغليفه في وسيطة دالة لا تتم الإشارة إليه من داخل تخصص وسيطة القالب، لذلك لا يزال يتم البحث عن الأعضاء الموروثين.

أعتقد أن الإجابة التي تبحث عنها موجودة هنا.

http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time

ومثال أكثر ملئًا قليلاً هنا

http://pastie.org/298994

أستخدم التقنية للكشف عن وجود داعم مشغل أوستريم << على الفصل المعني ثم قم بإنشاء جزء مختلف من التعليمات البرمجية اعتمادًا على ذلك.

لم أكن أعتقد أن ذلك ممكن قبل العثور على الحل المرتبط ولكنها خدعة رائعة جدًا.اقضِ الوقت في فهم الكود وهو أمر يستحق الكثير من الوقت.

براد

إذا كنت تستخدم facebook folly، فهذه الماكرو خارج الصندوق لمساعدتك:

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

على الرغم من أن تفاصيل التنفيذ هي نفسها مع الإجابة السابقة، إلا أن استخدام المكتبة أسهل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top