클래스에 주어진 서명의 멤버 함수가 있는지 확인


135

클래스에 주어진 서명의 특정 멤버 함수가 있는지 감지하기 위해 템플릿 트릭을 요청하고 있습니다.

문제는 여기 http://www.gotw.ca/gotw/071.htm에 인용 된 것과 유사 하지만 동일하지는 않습니다. Sutter의 책에서 그는 클래스 C가 멤버 함수를 제공해야한다는 질문에 대답했습니다. 특정 서명이 없으면 프로그램이 컴파일되지 않습니다. 내 문제에서 클래스에 해당 기능이 있으면 무언가를해야하고 그렇지 않으면 "다른 것"을해야합니다.

boost :: serialization이 비슷한 문제에 직면했지만 그들이 채택한 솔루션이 마음에 들지 않습니다. 특정 멤버 함수를 정의하지 않는 한 기본적으로 특정 서명으로 자유 함수 (정의해야 함)를 호출하는 템플릿 함수 ( 특정 서명과 함께 주어진 유형의 두 매개 변수를 사용하는 "직렬화"의 경우에는 컴파일 오류가 발생합니다. 즉, 침입 및 비 침입 직렬화를 모두 구현해야합니다.

나는 두 가지 이유로 그 솔루션을 좋아하지 않습니다.

  1. 방해가되지 않도록하려면 boost :: serialization 네임 스페이스에있는 전역 "serialize"함수를 재정의해야합니다. 따라서 클라이언트 코드에 네임 스페이스 부스트 및 네임 스페이스 직렬화를 열 수 있습니다!
  2. 그 혼란을 해결하기위한 스택은 10-12 개의 함수 호출이었습니다.

해당 멤버 함수가없는 클래스에 대한 사용자 정의 동작을 정의해야하며 엔티티가 다른 네임 스페이스 안에 있습니다 (그리고 다른 네임 스페이스에있는 동안 한 네임 스페이스에 정의 된 전역 함수를 재정의하고 싶지 않습니다)

이 퍼즐을 풀기위한 힌트를 주실 수 있습니까?



@ R.MartinhoFernandes 어떤 답변을 찾고 있습니까? Mike Kinghan의 답변 은 깊이 있고 C ++ 11을 사용하고 있습니다.
jrok

@ R.MartinhoFernandes 어쩌면 이것은 당신이 찾고있는 현대 버전입니까?
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
이게 뭐야 ?? 합법적 인 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 값을 열거 형 또는 정적 정수 상수로 유지합니다 (템플릿 <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {정적 const int value = N;})
David Rodríguez-dribeas

2
int_to_type 대신 boost :: integral_constant를 사용하십시오.
Vadim Ferderer

2
@JohanLundberg (비 정적) 멤버 함수에 대한 포인터입니다. 예를 들면 다음과 같습니다 size_t(std::vector::*p)() = &std::vector::size;.
복원 Monica 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"메소드가 존재하지 않으면 어떻게 false 값을 반환하는지 알 수 없습니다.
Collin

1
이 경우 @Collin 템플릿 매개 변수의 대체는 첫 번째 검사 과부하에 실패하고 과부하 세트에서 삭제됩니다. false_type을 반환하는 두 번째 것으로 돌아갑니다. SFINAE 원리로 인해 컴파일러 오류가 아닙니다.
jrok

1
@ elios264 없습니다. 매크로를 사용하여 확인하려는 각 기능에 대한 템플릿을 작성할 수 있습니다.
jrok

1
검사 인수가 T 또는 T &가 아닌 T * 유형 인 특별한 이유는 무엇입니까?
shibumi

1
그러나 serialize자체적으로 템플릿을 수락하면 어떻게 될까요? serialize정확한 유형을 입력하지 않고 존재 를 테스트하는 방법이 있습니까?
Hi-Angel

37

컴파일 타임 멤버 함수 내성에 대한이 질문에 대한 대답은 인기가 있지만 다음 프로그램에서 볼 수있는 걸림돌이 있습니다.

#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.

이 문제에 대해 현명하지 않다면 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*(). 그 이유 는 그 자체 가없고 템플릿 인스턴스화가 "상속을 수행하지"않기 때문입니다.

이 snag는 "sizeof () Trick"을 사용하여 잘 알려진 SFINAE 접근 방식에 영향을 미치지 않으며 T일부 멤버 함수가 있는지 여부 만 감지 합니다 mf(예 : 이 답변 및 설명 참조). 그러나 그 T::mf존재를 확립하는 것은 종종 (보통?) 충분하지 않습니다. 원하는 서명을 가지고 있는지 확인해야 할 수도 있습니다. 그것은 도시 된 기술이 점수를 매기는 곳입니다. 원하는 서명의 포인터 변형은 &T::mfSFINAE 프로브가 성공하기 위해 충족해야하는 템플리트 유형의 매개 변수에 새겨 져 있습니다 . 그러나이 템플릿 인스턴스화 기술 T::mf은 상속 될 때 잘못된 답변을 제공합니다 .

컴파일 타임 내부 검사를위한 안전한 SFINAE 기술은 SFINAE 함수 템플릿 해상도가 의존하는 유형을 인스턴스화하기 위해 템플릿 인수 내 T::mf에서 사용하지 않아야합니다 &T::mf. 대신 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 satifies입니다 과부하의 첫 번째 매개 변수 형식 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;
}

이 아이디어에 새로운 결함이 있습니까? 다시 피할 수없는 걸림돌의 파울없이 더 일반적으로 만들 수 있습니까?


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;

회원 열거 확인 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
해킹! yrp에서 제안한 솔루션과 비슷합니다. 멤버 함수를 통해 템플릿을 템플릿으로 만들 수 있다는 것을 몰랐습니다. 그것은 내가 오늘 배운 새로운 기능입니다! ... 그리고 새로운 교훈 : "당신이 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 레디 컴파일러와 호환 가능합니다 (따라서 decltype 없음)

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. type_traits헤더 의 메타 조건에 따라 과부하 를 반환 true_type하거나false_type 과부하 를 반환하려고합니다.
  3. " 예상 과부하 해결에서 줄임표 변환의 가장 낮은 우선 순위" 를 활용하기 위해 과부하가 예상 되는 과부하 및 Variadic 매개 변수가 활용해야하는 true_type과부하를 선언하십시오 .intfalse_type
  4. 에 대한 템플릿 사양을 정의에서 true_type우리가 사용하는 기능 declvaldecltype방법 사이의 반환 형식의 차이 또는 과부하의 기능의 독립을 감지하는 우리를 수

여기 에서 실제 예를 볼 수 있습니다 . 그러나 아래에서 설명하겠습니다.

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명명 된 메소드 가 없다)testint

이 솔루션에는 두 가지 단점이 있습니다.

  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생성 합니다.truefalse


3

방해 serialize받지 않기 위해 Koenig 조회 덕분에 직렬화되는 클래스 또는 아카이브 클래스의 네임 스페이스를 넣을 수도 있습니다 . 자세한 내용 은 자유 함수 재정의에 대한 네임 스페이스를 참조 하십시오. :-)

무료 기능을 구현하기 위해 지정된 네임 스페이스를 여는 것은 간단합니다. (예를 들어, 고유 한 유형 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 :: experimental을 사용할 수없는 경우 다음과 같이 기본 버전을 만들 수 있습니다.

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의 기사는 특성에 대해 이야기합니다. 따라서 기본 인스턴스화에 폴백 동작이있는 traits 클래스를 가질 수 있으며 멤버 함수가있는 각 클래스에 대해 traits 클래스는 멤버 함수를 호출하도록 특화되어 있습니다. 나는 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-trick의 기초를 밝힙니다.FooFindertrue_typefalse_type

TypeSink의 적분 결과 싱크 나중에 사용되는 템플릿 구조체 인 sizeof형태를 형성하기 위해 주형에 실체화 연산자.

match기능은 일반 대응 물없이 남겨진 SFINAE 종류의 템플릿입니다. 따라서 인수 유형이 특수 유형과 일치하는 경우에만 인스턴스화 할 수 있습니다.

test열거 형 선언과 함께 두 함수는 결국 중앙 SFINAE 패턴을 형성합니다. 줄임표를 사용하여 일반 false_type인수와 더 구체적인 인수가있는 상대 항목을 우선적으로 사용합니다.

이 instantiate 할 수 있으려면 test의 템플릿 인수 기능 Tmatch그것의 반환 유형을 인스턴스화하는 데 필요한 같은 기능은 인스턴스화해야 TypeSink인수를. 주의 할 점 &U::foo은 함수 인수로 래핑 되는 템플릿 인수 전문화 내에서 참조 되지 않으므로 상속 된 멤버 조회가 여전히 발생한다는 것입니다.


1

페이스 북 어리 석음을 사용하는 경우 다음과 같은 기능을 제공합니다.

#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

나는 비슷한 요구를 가지고 있었고이 o를 발견했다. 여기에 제안 된 많은 흥미롭고 강력한 솔루션이 있지만 특정 요구에 대해서는 조금 길지만 클래스에 정확한 서명을 가진 멤버 함수가 있는지 감지하십시오. 그래서 나는 약간의 읽기 / 테스트를하고 관심있는 내 버전을 생각해 냈습니다. 감지 :

  • 정적 멤버 함수
  • 비 정적 멤버 함수
  • 비 정적 멤버 함수 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.