2012-05-23 2 views
20

SFINAE을 사용하여 C++에 클래스가 있는지 검색 할 수 있습니까? 가능한 경우 어떻게?SFINAE를 사용하여 클래스의 존재를 감지하는 방법은 무엇입니까?

일부 라이브러리 버전에서만 제공되는 클래스가 있다고 가정합니다. SFINAE를 사용하여 클래스가 존재하는지 여부를 감지 할 수 있는지 알고 싶습니다. 검출 결과는 임의적입니다. 예를 들어 enum 상수는 존재하는 경우 1, 그렇지 않으면 0입니다.

+1

클래스에 특정 타이가 포함되어 있는지 검색하는 것을 의미합니다. 체육? 또는 네임 스페이스 범위에 유형이 있습니까? (나는 후자가 유용 할 방법을 모르겠다.) – sbabbi

+0

컴파일러가이를 수행 할 것이다. 당신이하고 싶은 것을 설명해주십시오. –

+0

명백하게하기 위해 - 클래스는 단지 하드 코딩 된 식별자이므로 템플릿 매개 변수 대체에 전혀 의존하지 않습니다. – ndkrempel

답변

25

컴파일러에게 T이 아니며 이 아닌 클래스 유형에 대해 알려주면 컴파일 오류가 발생합니다. 그 라운드는 입니다. 따라서 T 클래스가 존재하는지 여부를 알고 싶다면 T 이 아직 선언되지 않았을 수도 있으므로 T을 먼저 선언해야합니다.

그러나 단지 T를 선언하는 T 존재에 의해 우리가 의미해야하는지 때문에, 그것은 "존재"하지 않기 때문에 즉, OK이다는 T을 정의합니다. 그리고 T을 선언 한 경우 을 입력하면 으로 정의되었는지 여부를 확인할 수 있습니다. 에 혼란이있을 필요는 없습니다.

그래서 문제는 T가 정의 된 클래스 유형인지 여부를 확인하는 것입니다.

sizeof(T) 여기에는 도움이되지 않습니다. T이 정의되지 않으면 incomplete type T 오류가 발생합니다. 마찬가지로 typeid(T). T *T이없는 경우에도,만큼 T가 선언 된 같은 정의 형 입니다 때문도 아니다, 그것은 유형 T *에 SFINAE 프로브 공예 좋은 입니다. 그리고 은 T 클래스의 선언을해야하므로 은 답변이 아니기 때문에 "Yes"라고 말하는 것이 충분합니다.

C++ 11은 <type_traits>std::is_constructible<T ...Args>을 제공합니다. 이 제품은 페그 솔루션을 제공합니까? - T이 정의 된 경우 에 적어도 하나의 생성자가 있어야합니다.

나는 두려워하지 않습니다. 적어도 하나의 공개 생성자의 서명을 알고 있다면 T의 GCC의 <type_traits> (4.6.3 현재)은 실제로 의 사업을 수행합니다. 알려진 공공 생성자가 T::T(int)이라고합시다. 그런 다음 : T이 정의와 false의 경우 T이 단지 선언 된 경우

std::is_constructible<T,int>::value 

사실 일 것입니다.

하지만 이식성이 없습니다. 대부분 도착 않을 때 std::is_constructible이 따를 것이다 소송 : 2010 ++ VC에서 <type_traits> 아직 std::is_constructible 심지어 그 std::has_trivial_constructor<T>T 경우 발프가 정의되어 있지 않습니다를 제공하지 않습니다. 또한, 에 제공하기 위해 전용 생성자가 T 존재하는 경우에는 GCC 조차도 (눈썹을 높이는) barf가됩니다.

T이 정의 된 경우 소멸자과 소멸자가 하나만 있어야합니다. 그리고 소멸자는 다른 가능한 구성원 인 T보다 공개 될 가능성이 있습니다. 그 빛, 우리가 할 수있는 가장 간단하고 강한 플레이는 의 존재에 대한 SFINAE 프로브를 만드는 것입니다.

이 SFINAE 프로브는 T는 일반 멤버 함수 mf이 여부를 결정하기위한 일상적인 방법으로 제작 될 수 없다 - 용어 정의 된 인수를 취하는 SFINAE 프로브 기능의 "예 과부하" 을 만드는 &T::mf의 유형 왜냐하면 우리는 소멸자 (또는 생성자)의 주소를 가져갈 수 없기 때문입니다. T 정의 된 경우

그럼에도 불구하고 T::~T 타입 DT 못한 - 가 dtT::~T의 호출로 평가 식 때마다 decltype(dt) 의해 수득되어야한다; 그러므로 DT *은 또한 의 원칙에 따라 함수 오버로드의 인수 유형으로 제공 될 수있는 유형이 될 것입니다. T 법적 decltype(std::declval<A>().~A())의 인수 식에서 호출 될 수있는 공공 소멸자가 있어야에만 제한

#ifndef HAS_DESTRUCTOR_H 
#define HAS_DESTRUCTOR_H 

#include <type_traits> 

/*! The template `has_destructor<T>` exports a 
    boolean constant `value that is true iff `T` has 
    a public destructor. 

    N.B. A compile error will occur if T has non-public destructor. 
*/ 
template< typename T> 
struct has_destructor 
{ 
    /* Has destructor :) */ 
    template <typename A> 
    static std::true_type test(decltype(std::declval<A>().~A()) *) { 
     return std::true_type(); 
    } 

    /* Has no destructor :(*/ 
    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)) type; 

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

#endif // EOF 

: 그러므로 우리는 이 같은 프로브 (GCC 4.6.3)을 작성할 수 있습니다. (has_destructor<T> 내가 here 기여 방법-introspecting 템플릿의 간략화 적응이다.)

일부 모호한 구체적 std::declval<A>() 수 있다는 인자 발현 std::declval<A>().~A()의 의미.함수 서식 std::declval<T>()<type_traits>에서 정의 (T r- 수치로 참조)를 T&&를 반환한다 - 그것은 그러한 decltype의 인자로 평가되지 않은 컨텍스트에서 호출 될 수도있다. 따라서 std::declval<A>().~A()의 의미는 A 일 때 ~A()으로 전화하면 입니다. std::declval<A>()은 여기 공공 생성자 T의 필요성을 없애거나 그것에 대해 알기 위해 여기에 도움이됩니다.

따라서, "예 과부하"에 대한 SFINAE 프로브의 인수 유형은 다음과 같습니다 Atest<T>(0)의 소멸자의 유형에 포인터가 과부하 단지의 경우 이러한 유형 이 있음을 일치합니다 공개적으로 단단히 마음에 T의 파괴 값은 그것의 한계 - - 당신이 클래스 T인지 여부를 테스트 할 수 있습니다 A에 대한 A의 소멸자 = 손에 has_destructor<T>T

로 에 정의 된 코드 에 을 지정하여을 선언 한 후 질문을하십시오. 다음은 테스트 프로그램입니다. GCC 4.6.3와 내장

#include "has_destructor.h" 
#include <iostream> 

class bar {}; // Defined 
template< 
    class CharT, 
    class Traits 
> class basic_iostream; //Defined 
template<typename T> 
struct vector; //Undefined 
class foo; // Undefined 

int main() 
{ 
    std::cout << has_destructor<bar>::value << std::endl; 
    std::cout << has_destructor<std::basic_iostream<char>>::value 
     << std::endl; 
    std::cout << has_destructor<foo>::value << std::endl; 
    std::cout << has_destructor<vector<int>>::value << std::endl; 
    std::cout << has_destructor<int>::value << std::endl; 
    std::count << std::has_trivial_destructor<int>::value << std::endl; 
    return 0; 
} 

,이 소멸자가 2 개 // Defined 클래스와 2 개 // Undefined 클래스하지 않는 것을 당신에게 말할 것이다. 다섯 번째 출력 라인은 int이 파괴 가능하다고 말하며, 최종 라인은 std::has_trivial_destructor<int>이 동의 함을 나타냅니다. 을 사용하여 클래스 유형을 필드로 좁히려면 뒤에 std::is_class<T>을 적용하면 T이 파괴 가능하다고 판단됩니다.

Visual C++ 2010은 std::declval()을 제공하지 않습니다. 아니, SFINAE와

#ifdef _MSC_VER 
namespace std { 
template <typename T> 
typename add_rvalue_reference<T>::type declval(); 
} 
#endif 
+0

그건 참으로 좋은 분석입니다. 나는 그것이 특수 변환 생성자를 요구하는 대신에 훅으로서 소멸자를 사용한다는 것을 제외하고는 제안 된 솔루션과 실질적으로 동일하다고 생각한다. 트레이드 오프가 있습니다.이 솔루션은 대부분의 경우 투명하게 작동하지만, 각 클래스 정의에서 매크로를 호출하는 비용으로 항상 작동합니다. –

+0

안녕하세요, 훌륭한 답변, 클래스 템플릿을 사용하여 코드를 수정하려면 노력하고있어, 지금까지 'typename A/T'템플릿 ' 클래스 A/T' 내 템플릿의 인수와 일치시키지 만 공용 소멸자가있는 경우에도 테스트가 자동으로 실패합니다. 왜 그게 좋을지 알아? 나는 클래스에 템플릿을 인스턴스화하고 원본을 사용할 수 있지만 템플릿을 전문으로하기 전에 매크로에서이 체크를 사용하므로 내 전문화 전에 인스턴스화 할 수 없다는 것을 알고 있습니다. 고맙습니다! – stellarpower

+0

코드를 보지 않고도 말할 수 없습니다. 클래스 템플릿에 솔루션을 적용하는 것은 필자에게 똑바로 작동하므로 코드에 문제가있다. 적절한 제목으로 새로운 질문을하고 "이 솔루션을 클래스 템플릿 작업에 실패하게 만들려고합니다. 다음은 제 코드입니다 ..." –

0

좋아, 나는 더 나은 방법이 있을지 모르지만 나는 이것을하는 방법을 발견했다고 생각한다. 라이브러리의 일부 인스턴스에는 포함되어 있고 다른 인스턴스에는 포함되어 있지 않은 클래스 A가 있다고 가정합니다. 트릭은 A에 특수 개인 변환 생성자를 정의한 다음 SFINAE를 사용하여 변환 생성자를 검색하는 것입니다. A가 포함되면 탐지가 성공합니다. 그렇지 않으면 탐지가 실패합니다.

다음은 구체적인 예입니다. 먼저 감지 템플릿, class_defined.hpp의 헤더 :

struct class_defined_helper { }; 

template< typename T > 
struct class_defined { 

    typedef char yes; 
    typedef long no; 

    static yes test(T const &); 
    static no test(...); 

    enum { value = sizeof(test(class_defined_helper()) == sizeof(yes) }; 
}; 

#define CLASS_DEFINED_CHECK(type)  \ 
    type(class_defined_helper const &); \ 
             \ 
    friend struct class_defined<type>; 

이제 클래스 정의, blah.hpp 포함하는 헤더 :

#include "class_defined.hpp" 

#ifdef INCLUDE_BLAH 
class blah { 
    CLASS_DEFINED_CHECK(blah); 
}; 
#else 
class blah; 
#endif 

이제 소스 파일을 MAIN.CPP :

#include "blah.hpp" 

int main() { 
    std::cout << class_defined<blah>::value << std::endl; 
} 

BLAH_INCLUDED를 사용하여 컴파일 됨이 인쇄가 정의되어 있습니다. 1. BLAH_INCLUDED가 정의되어 있지 않으면 0이 인쇄됩니다. 불행하게도이 경우 여전히 두 경우 모두 컴파일 할 클래스의 순방향 선언이 필요합니다. 나는 그것을 피할 수있는 방법이 보이지 않습니다.

+1

이것은 타입이 정의되어 있는지 (심지어 선언되어 있든) 실제로는 감지하지 않고, 매크로가 정의되어 있는지 여부를 감지합니다. 이 경우 모든 코드를 삭제하고 '#if CLASS_DEFINED_MACRO' 및 종속 코드로 두는 것이 더 간단 할 수 있습니다. –

+0

나는 따라 가지 않는다. 요점은 형식을 선언해야하는지 여부를 확인하기 위해 형식을 선언했는지 여부를 확인할 수 없다는 것이 었습니다. 타입에서 찾을 훅이 없다면 타입이 정의되어 있는지 결정하는 것도 불가능하다고 생각합니다. 매크로는 변환 생성자의 형태로 후크를 제공합니다. 내 솔루션을 사용하면 클래스 정의의 유무에 관계없이 라이브러리를 컴파일하고 SFINAE를 사용하여 클래스가 정의되어 있는지 확인할 수 있습니다. 그것은 우리가 OP가 원하는 것을 얻을 수 있다고 생각하는만큼 가깝습니다. 많은 패턴에는 유형에 후크가 필요합니다. 그게 우리 매크로 다. –

+0

SFINAE와는 아무런 관련이 없습니다. –

5

: 당신이 has_destructor.h의 맨 위에 다음을 추가 할 수 있습니다 그 컴파일러를 지원합니다. 나는 이름 조회 트릭이 이것을 완성하는 방법이라고 생각한다. 당신은 라이브러리의 이름 공간에 이름을 주입하는 것을 두려워하지 않은 경우 :

#if DEFINE_A 
class A; 
#endif 

namespace { 
    struct local_tag; 
    using A = local_tag; 
} 

namespace foo { 
    template <typename T = void> 
    ::A is_a_defined(); 
} 

constexpr bool A_is_defined = 
    !std::is_same<local_tag, decltype(foo::is_a_defined())>::value; 

Demo.

: A 글로벌 네임 스페이스에 선언

namespace lib { 
#if DEFINE_A 
class A; 
#endif 
} 

namespace { 
    struct local_tag; 
    using A = local_tag; 
} 

namespace lib { 
    template <typename T = void> 
    A is_a_defined(); 
} 

constexpr bool A_is_defined = 
    !std::is_same<local_tag, decltype(lib::is_a_defined())>::value; 

Demo.

경우

2

아직 했음 이 게시물에서 만족스러운 답변을 찾지 못했습니다 ...

마이크 Kinghan는 응답 권리를 시작하고 스마트 것은 말 : 그래서 문제가 T가 정의 된 클래스 형식인지 여부를 확인하는 것입니다

합니다.

그러나

를 sizeof (T)는

여기

당신이 sizeof(T) 함께 할 수있는 방법은 ... 올바른 더 여기에 도움

하지 않은 것입니다 :

template <class T, class Enable = void> 
struct is_defined 
{ 
    static constexpr bool value = false; 
}; 

template <class T> 
struct is_defined<T, std::enable_if_t<(sizeof(T) > 0)>> 
{ 
    static constexpr bool value = true; 
}; 
+0

이 대답은 이해하기 쉽고 효과적입니다. 나를 위해 GCC 7.2.1과 VS 2017.최상위 정답은 GCC에서 "incomplete type"오류를줍니다. Kudos for this :) – Kai

관련 문제