클래스 멤버 함수가 있는지 템플릿으로 검사 했습니까?


498

특정 멤버 함수가 클래스에 정의되어 있는지 여부에 따라 동작을 변경하는 템플릿을 작성할 수 있습니까?

다음은 내가 쓰고 싶은 간단한 예입니다.

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

경우에 따라서, class TtoString()정의하고 그것을 사용; 그렇지 않으면 그렇지 않습니다. 내가 모르는 마법의 부분은 "FUNCTION_EXISTS"부분입니다.


6
물론 아래 템플릿 응답은 컴파일 타임 정보에서만 작동합니다. 즉 T에는 toString이 있어야합니다. toString을 정의하지만 T는 not을 정의하는 T의 서브 클래스를 전달하면 toString 정의되지 않았다는 메시지가 표시됩니다.
Alice Purcell

답변:


319

예, SFINAE를 사용하면 주어진 클래스가 특정 메소드를 제공하는지 확인할 수 있습니다. 작동 코드는 다음과 같습니다.

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

방금 Linux 및 gcc 4.1 / 4.3으로 테스트했습니다. 다른 컴파일러를 실행하는 다른 플랫폼으로 이식 가능한지 모르겠습니다.


18
비록 '1'과 '2'에 대해 다음을 사용했습니다. typedef char Small; 플랫폼 종속 변수 크기에 대한 모호성을 보장하지 않는 Big {char dummy [2];} 클래스.
user23167

6
나는 (긴) sizeof 연산자 == sizeof의 (문자)와 함께 땅에 플랫폼이 존재 의심
니콜라 Bonelli

17
나는 확실하지 않지만 이것이 휴대용이라고 생각하지 않습니다. typeof는 GCC 확장이며 다른 컴파일러에서는 작동하지 않습니다.
레온 티머 맨스

56
typeof는 필요하지 않습니다-char [sizeof (& C :: helloworld)]도 작동합니다. sizeof (long) == sizeof (char)를 피하려면 struct {char [2]};를 사용하십시오. 크기는> = 2 여야합니다
MSalters

57
대체 : 사소한,하지만 알아낼 걸 렸어요 typeof으로 decltype사용하는 경우 C + +0 -std = C + +0를 통해, 예를.
16:06

264

이 질문은 오래되었지만 C ++ 11에서는 SFINAE에 다시 의존하여 함수 존재 여부 (또는 실제로 유형이 아닌 멤버의 존재 여부)를 확인하는 새로운 방법이 있습니다.

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

이제 몇 가지 설명을하겠습니다. 첫 번째로, 내부의 첫 번째 표현식 이 유효하지 않은 경우 (일명 함수가 존재하지 않는 경우) SFINAE 표현식을 사용 serialize(_imp)하여 과부하 해결 에서 함수 를 제외합니다 decltype.

void()모든 함수의 반환 형식을 만드는 데 사용됩니다 void.

0인수는 선호하는 데 사용됩니다 os << obj모두 (문자 그대로 사용할 수있는 경우 과부하를 0유형 인 int과 상기 제 1 과부하로 더 나은 일치).


이제 함수가 존재하는지 확인하는 특성을 원할 것입니다. 운 좋게도 작성하기 쉽습니다. 그러나 원하는 모든 함수 이름마다 특성을 직접 작성해야합니다 .

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

라이브 예.

그리고 설명에. 먼저 sfinae_true도우미 유형이며 기본적으로 writing과 동일합니다 decltype(void(std::declval<T>().stream(a0)), std::true_type{}). 장점은 더 짧다는 것입니다.
다음에, struct has_stream : decltype(...)하나의 상속 std::true_type또는 std::false_type결국은 여부에 따라 decltype체크가 test_stream실패하거나하지.
마지막으로, std::declval전달할 수있는 방법에 대해 알 필요없이 전달하는 모든 유형의 "값"을 제공합니다. 이 예와 같은 평가되지 않은 상황 안에서만 가능 유의 decltype, sizeof등.


참고 decltype필요로 필요하지 않습니다 sizeof(모든 평가되지 않은 컨텍스트)이 향상되었다. 그것은 단지의 decltype이미 유형을 제공하며, 같은 단지 청소기입니다. 다음 sizeof은 과부하 중 하나의 버전입니다.

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

intlong매개 변수는 같은 이유로 여전히 있습니다. 배열 포인터는 사용될 수있는 컨텍스트를 제공하는 sizeof데 사용됩니다.


4
decltypeover 의 장점은 sizeof함수 호출에 대해 특수하게 조작 된 규칙에 의해 임시가 도입되지 않는다는 것입니다 (따라서 리턴 유형의 소멸자에 대한 액세스 권한이 필요하지 않으며 리턴 유형이 클래스 템플릿 인스턴스화).
Johannes Schaub-litb

5
Microsoft는 아직 C ++ 컴파일러로 Expression SFINAE를 구현하지 않았습니다. 왜 이것이 나를 위해 작동하지 않는지 혼란스러워서 사람들의 시간을 절약하는 데 도움이 될 수 있다고 생각하십시오. 멋진 솔루션이지만 Visual Studio에서 사용하기를 기다릴 수 없습니다!
Jonathan

3
첫 번째 예제 링크가 끊어짐
NathanOliver

1
그것은 즉, 말할 수하는 static_assert(has_stream<X, char>() == true, "fail X");그 행동이 원하는 모든 인수의 형태가 난 것을 달성 할 수있는 방법을 잘 모릅니다 일치하지됩니다 그렇다면이 아닌 문자가 int와 convertable 때문에 주장 컴파일 할 것인가?
Gabriel

4
내가 당신이 decltype에 대한 두 가지 주장에 대해 당황한 경우 : decltype은 실제로 하나만 취합니다. 쉼표는 여기에 연산자입니다. 참조 stackoverflow.com/questions/16044514/...
앙드레

159

C ++를 사용하면 SFINAE 를 사용할 수 있습니다 (C ++ 11 기능의 경우 거의 임의의 표현식에서 확장 SFINAE를 지원하기 때문에이 방법이 더 간단하다는 점에 유의하십시오. 아래는 일반적인 C ++ 03 컴파일러에서 작동하도록 제작되었습니다).

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

위의 템플릿과 매크로는 템플릿을 인스턴스화하여 멤버 함수 포인터 유형과 실제 멤버 함수 포인터를 제공합니다. 유형이 맞지 않으면 SFINAE는 템플릿을 무시합니다. 이와 같은 사용법 :

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

그러나 toStringif 분기에서 해당 함수를 호출 할 수는 없습니다 . 컴파일러는 두 가지 모두에서 유효성을 검사하므로 함수가 존재하지 않는 경우 실패합니다. 한 가지 방법은 SFINAE를 다시 한 번 사용하는 것입니다 (enable_if도 boost에서 얻을 수 있음).

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

그것을 사용하여 재미있게 보내십시오. 그것의 장점은 오버로드 된 멤버 함수와 const 멤버 함수에서도 작동한다는 것입니다 ( std::string(T::*)() const멤버 함수 포인터 유형으로 사용하십시오!).


7
type_check서명이 정확히 일치하도록하는 방법 이 마음에 듭니다. 서명 Sign이 있는 메소드를 호출 할 수있는 방식으로 호출 할 수있는 메소드와 일치하도록하는 방법이 있습니까? (예 : Sign= std::string(T::*)()이면 std::string T::toString(int default = 42, ...)일치 하도록 허용 )
j_random_hacker 1

5
나는 나에게 즉각적으로 명백하지 않은 이것에 대해 무언가를 알아 내기 때문에 다른 사람들을 도울 수 있습니다. sizeof 연산자는 chk를 호출 할 필요없이 chk의 출력 크기를 결정합니다.
SCFrench

3
@ deek0146 : 예, T포인터 - 투 - 메소드의-T 선언 SFINAE 적용되지 않습니다 및 IMO 가장 쉬운 솔루션으로 결합하는 것 이외의 클래스 T. 위해 밖으로 오류 때문에, 원시 형이 아니어야 is_class에서 확인 후원.
Jan Hudec

2
toString템플릿 기능인 경우 어떻게이 작업을 수행 할 수 있습니까?
Frank

4
Boost에서 이것이 (또는 동등한 것) 있습니까?
Dan Nissenbaum

89

C ++ 20- requires표현식

C ++ 20 에는 함수의 존재 여부를 확인하는 기본 제공 방법 인 requires표현식 과 같은 다양한 도구와 개념 이 있습니다. 그것들을 사용하면 optionalToString다음과 같이 함수를 다시 작성할 수 있습니다.

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pre-C ++ 20-감지 툴킷

N4502 는 C ++ 17 표준 라이브러리에 포함시킬 수있는 탐지 툴킷을 제안하여 결국 라이브러리 기본 TS v2에 포함 시켰습니다. 그 requires이후 로 표현식 에 포함되어 있기 때문에 표준에 도달하지 못할 가능성이 있지만 여전히 다소 우아한 방식으로 문제를 해결합니다. 툴킷에는 std::is_detected유형 또는 기능 감지 메타 기능을 쉽게 작성하는 데 사용할 수있는 일부 메타 기능이 도입되었습니다 . 사용 방법은 다음과 같습니다.

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

위의 예제는 테스트되지 않았습니다. 탐지 툴킷은 아직 표준 라이브러리에서 사용할 수 없지만 제안에는 실제로 필요한 경우 쉽게 복사 할 수있는 전체 구현이 포함되어 있습니다. C ++ 17 기능으로 훌륭하게 재생됩니다 if constexpr.

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return "toString not defined";
}

C ++ 14-Boost.Hana

Boost.Hana는 분명히이 특정 예제를 기반으로하며 설명서에 C ++ 14에 대한 솔루션을 제공하므로 직접 인용하겠습니다.

[...] Hana는 is_validC ++ 14 일반 람다와 결합하여 같은 것을 훨씬 더 깔끔하게 구현할 수 있는 기능을 제공합니다 .

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

이것은 has_toString주어진 표현식이 우리가 전달한 인수에 유효한지 여부를 반환 하는 함수 객체 를 남깁니다 . 결과는로 반환 IntegralConstant되므로 함수 결과는 어쨌든 유형으로 표시되므로 constexpr-ness는 문제가되지 않습니다. 이제는 덜 장황한 것 (하나의 라이너입니다!) 외에도 의도가 훨씬 명확합니다. 다른 이점은 has_toString고차 알고리즘으로 전달 될 수 있고 함수 범위에서 정의 될 수 있다는 점입니다. 따라서 구현 세부 사항으로 네임 스페이스 범위를 오염시킬 필요가 없습니다.

부스트 .TTI

또 다른 다소 관용적 툴킷은 이러한 검사를 수행하는 - 비록 덜 우아 -이다 Boost.TTI 부스트 1.54.0에 도입은. 예를 들어, 매크로를 사용해야합니다 BOOST_TTI_HAS_MEMBER_FUNCTION. 사용 방법은 다음과 같습니다.

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

그런 bool다음를 사용하여 SFINAE 검사를 작성할 수 있습니다 .

설명

매크로 는 확인 된 유형을 첫 번째 템플리트 매개 변수로 BOOST_TTI_HAS_MEMBER_FUNCTION사용하는 메타 has_member_function_toString함수를 생성합니다. 두 번째 템플릿 매개 변수는 멤버 함수의 반환 형식에 해당하며 다음 매개 변수는 함수 매개 변수의 형식에 해당합니다. 멤버 는 클래스 에 멤버 함수가 있는지를 value포함 합니다 .trueTstd::string toString()

또는 has_member_function_toString멤버 함수 포인터를 템플릿 매개 변수로 사용할 수 있습니다. 따라서, 대체 가능 has_member_function_toString<T, std::string>::value하여 has_member_function_toString<std::string T::* ()>::value.


1
03
ZFY

@ ZFY Boost.TTI는 C ++ 03에서도 작동한다고 생각하지만 가장 우아한 솔루션은 아닙니다.
Morwenn

C ++ 20 솔루션이 실제로 유효합니까? 나는 그것을 원하지만-g ++과 msvc에 의해 거부된다-clang에서만 허용됩니다.
Bernd Baumanns

cppreference에서 다음을 읽을 수 있습니다. require-expression에 요구 사항에 유효하지 않은 유형 또는 표현식이 포함되어 있고 템플리트 엔티티의 선언 내에 표시되지 않으면 프로그램이 잘못된 것입니다.
Bernd Baumanns

@BerndBaumanns 정말? GCC 트렁크와 함께 작동하도록 했습니다 : godbolt.org/z/CBwZdE 어쩌면 당신이 옳을 수도 있지만, 그것이 제대로 작동하는지 확인했지만 표준 문구에 따라 합법적인지 여부는 확인하지 않았습니다.
Morwenn

56

이 질문은 2 살이지만 감히 답변을 추가하겠습니다. 바라건대 이전의 확실한 해결책을 분명히하기를 바랍니다. 나는 Nicola Bonelli와 Johannes Schaub의 매우 유용한 답변을 가져 와서 IMHO, 더 읽기 쉽고 명확하며 typeof확장이 필요없는 솔루션으로 병합했습니다 .

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

gcc 4.1.2로 확인했습니다. 크레딧은 주로 Nicola Bonelli와 Johannes Schaub에게 전달되므로 내 답변이 도움이된다면 투표하십시오. :)


1
궁금한 점이 아래 Konrad Rudolph의 솔루션이하지 않는 일을합니까?
Alastair Irvine

3
@AlastairIrvine,이 솔루션은 모든 논리를 숨기고 Konrad는 사용자에게 부담을줍니다. Konrad의 솔루션은 짧고 읽기가 쉽지만, 각 클래스마다 별도의 템플릿 전문화가 필요합니다 toString. 일반 라이브러리를 작성하는 경우 클래스와 함께 작업하기를 원한다면 (부스트와 같은 것을 생각하십시오) 사용자에게 모호한 템플릿의 추가 전문화를 정의하도록 요구하는 것은 용납되지 않을 수 있습니다. 때로는 공용 인터페이스를 최대한 간단하게 유지하기 위해 매우 복잡한 코드를 작성하는 것이 좋습니다.
FireAphis

30

C ++ 11을위한 간단한 솔루션 :

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

3 년 후 업데이트 : (테스트되지 않음). 존재를 테스트하기 위해 이것이 효과가 있다고 생각합니다.

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

4
이것은 간단하고 우아하지만 엄격하게 말하면 OP의 질문에 대답하지 않습니다. 호출자가 함수의 존재 를 확인할 수 있도록하지 않고 항상 제공 하십시오. 그러나 어쨌든 좋다.
Adrian W

@AdrianW, 좋은 지적입니다. 내 답변을 업데이트했습니다. 나는 그것을 테스트하지 않았다
Aaron McDaid

다른 사람에게 도움이되는 경우 template<typename>가변성 과부하가 발생하기 전에는 이 작업을 수행 할 수 없었 습니다. 해결을 위해 고려되지 않았습니다.
Laboratorio Cobotica

다시, 이것은 유효하지 않은 C ++ 11입니다.
Peter

29

이것이 바로 유형 특성입니다. 불행히도, 그것들은 수동으로 정의되어야합니다. 귀하의 경우 다음을 상상하십시오.

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

5
정적 상수 대신 특성에 대해 열거 형을 선호해야합니다. "정적 상수 멤버는 lvalue로, 컴파일러가 정적 멤버에 대한 정의를 인스턴스화하고 할당하도록합니다. 결과적으로 계산은 더 이상 순수한"컴파일 타임으로 제한되지 않습니다. "효과."
Özgür

5
"계산 값은 lvalue가 아닙니다 (즉, 주소가 없습니다). 따라서 참조로 전달할 때 정적 메모리는 사용되지 않습니다. 계산 된 값을 리터럴로 전달한 것과 거의 같습니다. 이러한 고려 사항으로 인해 열거 형 값을 사용하게되었습니다. "C ++ 템플릿 : 전체 가이드
Özgür

22
Comptrol : 아니요, 인용 유형은 정수 유형 정적 상수가 특별한 경우이므로 여기에 적용되지 않습니다! 그들은 여기 열거 형과 똑같이 행동 하며 선호되는 방법입니다. 오래된 enum 핵은 C ++ 표준을 따르지 않는 컴파일러에서만 필요했습니다.
Konrad Rudolph

3
@Roger Pate :별로. 여기서 "프로그램에 사용됨"은 "참조 됨"과 동의어입니다. 이 구절에 대한 일반적인 내용과 모든 최신 C ++ 컴파일러가 구현 한 내용은 선언 할 필요없이 정적 상수 의 을 취할 수 있다는 것입니다 (이전 문장에서는 다음과 같이 말합니다.“… 멤버는 정수 상수 표현으로 나타날 수 있음) …”). 당신은 단지 당신의 주소를 취할 경우 (명시 적으로 통해 그것을 정의 할 필요가 &T::x또는 암시 적으로 참조에 바인딩하여).
Konrad Rudolph


25

글쎄,이 질문에는 이미 많은 답변 목록이 있지만 Morwenn의 의견을 강조하고 싶습니다 .C ++ 17에 대한 제안이 훨씬 간단합니다. 자세한 내용은 N4502 를 참조하십시오. 그러나 독립형 예제로 다음을 고려하십시오.

이 부분은 일정한 부분이므로 헤더에 넣으십시오.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

그런 다음 원하는 부분 (유형, 멤버 유형, 함수, 멤버 함수 등)을 지정하는 변수 부분이 있습니다. OP의 경우 :

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

N4502 에서 가져온 다음 예제 는보다 정교한 프로브를 보여줍니다.

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

위에서 설명한 다른 구현과 비교할 때, 이것은 매우 간단합니다. 줄어든 도구 ( void_tdetect)가 충분하고 털이 많은 매크로가 필요하지 않습니다. 또한 이전 접근법보다 훨씬 더 효율적 (컴파일 타임 및 컴파일러 메모리 소비) 인 것으로보고되었습니다 ( N4502 참조 ).

다음은 실제 예 입니다. Clang에서는 잘 작동하지만 불행히도 5.1 이전의 GCC 버전은 C ++ 11 표준에 대한 다른 해석을 void_t수행하여 예상대로 작동하지 않았습니다. Yakk 이미 제공 한 해결 방법 : 다음과 같은 정의를 사용 void_t( 하지만 반환 형식으로 매개 변수 목록의 작품 void_t을 ) :

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

비 멤버 기능을 감지하도록 확장 할 수 있습니까?
plasmacel

물론이지. 예제를주의 깊게 살펴보십시오. 기본적으로 표현식을 제공하고 유효한지 확인하십시오. 이 표현식이 멤버 함수 호출에 관한 것일 필요는 없습니다.
akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf )는 미래의 길입니다 ... 나는 유형에 대한 물건을 감지하는 깔끔한 방법을 찾고 있었고 N4502는 길입니다 토고.
tlonuk

11

"만약 X를한다면 컴파일 하시겠습니까?"라는 일반적인 문제에 대한 C ++ 11 솔루션입니다.

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

특성 has_to_string 되도록 has_to_string<T>::valuetrue경우에만, T방법 갖는 .toString이러한 상황에서 0 인자로 호출 될 수있다.

다음으로 태그 디스패치를 ​​사용합니다.

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

복잡한 SFINAE 표현보다 유지 관리가 쉬운 경향이 있습니다.

이러한 특성을 매크로로 작성하면 스스로 할 수 있지만 상대적으로 단순하기 때문에 (각각 몇 줄씩) 가치가 없습니다.

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

위의 작업은 매크로를 만드는 것 MAKE_CODE_TRAIT입니다. 원하는 특성의 이름과 유형을 테스트 할 수있는 코드를 전달합니다.T . 그러므로:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

위의 특성 클래스를 만듭니다.

게다가, 위의 기술은 MS가 "표현 SFINAE"라고 부르는 것의 일부이며, 2013 년 컴파일러는 상당히 실패합니다.

C ++ 1y에서는 다음과 같은 구문이 가능합니다.

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

많은 C ++ 기능을 악용하는 인라인 컴파일 조건부 분기입니다. 코드 인라인의 이점은 비용이 가치가 없지만 (어떻게 작동하는지 이해하지 못하는 사람) 옆에는 가치가 없지만 위의 솔루션의 존재는 관심이있을 수 있으므로 그렇게하는 것은 가치가 없습니다.


개인 사건을 처리합니까?
tower120

@ tower120 나는 실험해야 할 것입니다 : 템플릿이 개인 / 공개 / 보호 된 템플릿과 상호 작용하는 방법은 약간 모호합니다. has_to_string그러나 어디에서 호출하는지는 중요하지 않습니다 .
Yakk-Adam Nevraumont

하지만 다른 쪽에서 보면 파생 클래스에서 보호 된 멤버에게 연락 할 수 있습니다. 어쩌면이 모든 것들을 INSIDE 클래스에 넣고 구조체에서 constexpr 함수로 변환하면 ...
tower120

여기,이 coliru.stacked-crooked.com/a/ee94d16e7c07e093를 보십시오. 그냥 constexpr로 만들 수 없습니다
tower120


10

다음은 일부 사용법 스 니펫입니다. *이 모든 것에 대한 용기는 더 먼

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
우리 sig_check<func_sig, &T::func_name>가 자유 함수 검사로 변경하면 왜 우리가 검사 sig_check<func_sig, &func_name>하려는 함수의 이름을 언급하는 "선언되지 않은 식별자"로 빌드하지 못하는지 알고 있습니까? SFINAE가 오류가 아닌 것으로 기대하기 때문에 회원들에게는 무료 기능이 아닌 이유는 무엇입니까?
v.oddou

나는 자유 함수가 클래스 또는 구조체가 아니라는 사실과 관련이 있다고 가정합니다. 멤버의 존재를 추론하는이 기술은 실제로 C ++의 다중 상속 메커니즘에 중점을두고 있습니다. 실제로 확인하려는 멤버와 실제로 멤버를 확인하는 클래스를 비교할 목적으로 만 존재하는 스텁 클래스간에 모호성을 강제합니다. 그래도 흥미로운 질문입니다. 다른 C ++ 11/14 멤버 확인 기술을 확인할 수 있습니다. 새로운 표준에서 영리한 것들을 보았습니다.
Brett Rossier

귀하의 답변에 감사드립니다. 상속에 대해 인텔이 제공하는 깊이를 더 자세히 확인해야 할 수도 있습니다. 지금까지 SFINAE에 의존하여 표현에 대한 액세스를 올바르게 표현하지 못하는 표현 사이의 상관 관계를 보지 못했기 때문에 템플릿 유형 매개 변수의 멤버 및 다중 상속 그러나 나는 C ++에서 먼 개념조차도 서로에게 번질 수 있다고 완전히 믿습니다. 이제 무료 기능에 대해서는이 질문이 흥미 롭습니다. stackoverflow.com/questions/26744589 TC 답변은 "선언되지 않은 식별자"를 피하기 위해 더미를 선언하는 트릭을 사용하는 것 같습니다
v.oddou

8

위의 솔루션과 달리 상속 된 멤버 함수를 확인하는 다른 스레드에서 이에 대한 답변을 작성했습니다.

상속 된 멤버 함수를 확인하는 SFINAE

해당 솔루션의 일부 예는 다음과 같습니다.

예 1 :

다음 서명이있는 회원을 확인 중입니다. T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

메소드의 일관성을 확인하고 기본 유형과도 작동합니다. (내말은has_const_begin<int>::value 즉, 거짓이며 컴파일 타임 오류가 발생하지 않습니다.)

실시 예 2

이제 서명을 찾고 있습니다 : void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

MyClass가 기본적으로 구성 가능하거나 특별한 개념을 만족시킬 필요는 없습니다. 이 기술은 템플릿 멤버와도 작동합니다.

이에 대한 의견을 간절히 기다리고 있습니다.


7

이제 이것은 좋았습니다 작은 퍼즐 -좋은 질문입니다!

다음은 비표준 연산자 에 의존하지 않는 Nicola Bonelli 솔루션의 대안 typeof입니다.

불행히도 GCC (MinGW) 3.4.5 또는 Digital Mars 8.42n에서는 작동하지 않지만 모든 버전의 MSVC (VC6 포함) 및 Comeau C ++에서는 작동합니다.

더 긴 주석 블록에는 작동 방식 (또는 작동 예정)에 대한 세부 정보가 있습니다. 그것이 말하듯이, 어떤 행동이 표준을 준수하는지 잘 모르겠습니다. 이에 대한 논평을 환영합니다.


업데이트-2008 년 11 월 7 일 :

이 코드는 문법적으로 정확하지만, 행동이 MSVC와 꼬모 C ++ 쇼 (덕분에 표준 따르지 않는 것 같습니다 레온 Timmermanslitb을 올바른 방향으로 날을 가리키는 경우). C ++ 03 표준은 다음과 같이 말합니다.

14.6.2 종속 이름 [temp.dep]

단락 3

클래스 템플릿 또는 클래스 템플릿의 멤버 정의에서 클래스 템플릿의 기본 클래스가 템플릿 매개 변수에 의존하는 경우 클래스의 정의 시점에서 정규화되지 않은 이름을 찾는 동안 기본 클래스 범위가 검사되지 않습니다. 템플릿 또는 멤버 또는 클래스 템플릿 또는 멤버의 인스턴스화 중.

따라서 MSVC 또는 Comeau 가 호출 사이트에서 이름 조회를 수행 하는 toString()멤버 기능을 고려할 때TdoToString() 가 템플릿을 인스턴스화 할 때 잘못된 것입니다 (실제로이 경우 내가 찾은 동작 임에도 불구하고).

GCC와 Digital Mars의 동작은 올바른 것으로 보입니다. 두 경우 모두 비 멤버 toString()함수가 호출에 바인딩됩니다.

쥐-나는 영리한 해결책을 찾았을 것이라고 생각했지만 대신 몇 가지 컴파일러 버그를 발견했습니다 ...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}

1
아니요, 표준을 준수하지는 않지만 -fpermissive 옵션을 설정하면 GCC에서 작동한다고 생각합니다.
Leon Timmermans

나는 그 의견이 많은 공간을 제공하지는 않지만 표준을 준수하지 않는 이유에 대한 정보를 지적 할 수 있습니까? (나는 논쟁하지 않고있다 – 나는 궁금하다)
Michael Burr

Mike B : 표준에 따르면 3.10 p15 : "프로그램이 다음 유형 중 하나 이외의 lvalue를 통해 객체의 저장된 값에 액세스하려고하면 동작이 정의되지 않습니다." 하다.
Johannes Schaub-litb

4
왜 내 의견을 추가하지 않는지 잘 모르겠습니다. toString 호출이 자격이 없습니다. 따라서 기본 클래스는 템플릿 유형 매개 변수에 의존하기 때문에 항상 무료 함수를 호출하지만 기본 함수는 호출하지 않습니다.
Johannes Schaub-litb

@litb : 포인터 주셔서 감사합니다. 3.10이 여기에 적용되지 않는다고 생각합니다. doToString () 내부의 toString () 호출이 "lvalue를 통해 객체의 저장된 값에 액세스하고 있지 않습니다". 그러나 두 번째 의견은 정확합니다. 답변을 업데이트하겠습니다.
Michael Burr

6

litb에서 제시 한 표준 C ++ 솔루션은 메소드가 기본 클래스에서 정의되면 예상대로 작동하지 않습니다.

이 상황을 처리하는 솔루션은 다음을 참조하십시오.

러시아어 : http://www.rsdn.ru/forum/message/2759773.1.aspx

Roman.Perepelitsa의 영어 번역 : http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

정말 영리합니다. 그러나이 솔루션의 한 가지 문제점은 테스트중인 유형이 기본 클래스 (예 : 기본 유형)로 사용할 수없는 유형 인 경우 컴파일러 오류를 발생시키는 것입니다.

Visual Studio에서 인수가없는 메서드로 작업하는 경우 인수 주위에 여분의 여분 () 쌍을 삽입하여 sizeof 표현식에서 deduce ()해야합니다.


흠, 그 게시물 아이디어를 사용하여 내 자신의 버전을 개발 한 결과, 아이디어에 다른 단점이 있으므로 코드를 다시 내 답변에서 제거했습니다. 하나는 모든 기능이 대상 유형에서 공개되어야한다는 것입니다. 따라서이 함수에서 "f"함수를 확인할 수 없습니다. 함수 struct g { void f(); private: void f(int); };중 하나가 개인용이기 때문입니다 (이는 코드가 수행하기 때문에 액세스 할 수없는 using g::f;경우 실패 함 f).
Johannes Schaub-litb

6

MSVC에는 __if_exists 및 __if_not_exists 키워드가 있습니다 ( Doc )가 있습니다. Nicola의 SFINAE 접근 방식과 함께 OP와 같은 GCC 및 MSVC 검사를 만들 수있었습니다.

업데이트 : 소스는 여기 에서 찾을 수 있습니다


6

Has_foo개념 점검 을 작성하여 SFINAE 및 템플리트 부분 특수화를 사용하는 예 :

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

template<typename T, typename = void> struct Has_foo: std::false_type{};

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};


static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

5

https://stackoverflow.com/a/264088/2712152에 제공된 솔루션을 수정했습니다. 에서 좀 더 일반적인 것으로 수정했습니다. 또한 새로운 C ++ 11 기능을 사용하지 않으므로 이전 컴파일러에서 사용할 수 있으며 msvc에서도 작동해야합니다. 그러나 컴파일러는 가변형 매크로를 사용하므로 C99가이를 사용할 수 있도록해야합니다.

다음 매크로를 사용하여 특정 클래스에 특정 typedef가 있는지 여부를 확인할 수 있습니다.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

다음 매크로를 사용하여 특정 클래스에 특정 멤버 함수가 있는지 또는 주어진 수의 인수가 없는지 여부를 확인할 수 있습니다.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

위의 두 매크로를 사용하여 has_typedef 및 has_mem_func에 대한 검사를 다음과 같이 수행 할 수 있습니다.

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

템플리트 인수로 멤버 함수를 지원하도록이를 개선 할 수 있습니다. <typename T> 템플릿을 <typename T, typename ... Args> 템플릿으로 변경 한 다음 매크로 줄임표에서 "Args ..."를 사용하여 가변 템플릿 인수가 포함 된 검사 구조체를 만들 수 있습니다. 예. "void onNext (const T &)"방법 검출 HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic

4

이상한 사람은 내가이 사이트에서 한 번 본 다음의 멋진 트릭을 제안하지 않았습니다.

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

T가 클래스인지 확인해야합니다. foo 조회의 모호성은 대체 실패 인 것으로 보입니다. 나는 그것이 표준인지 확실하지 않은 gcc에서 작동하게했다.


3

다음과 같은 유형으로 일부 "기능"이 지원되는지 확인하는 데 사용할 수있는 일반 템플릿 :

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

foo서명과 호환 되는 메소드가 있는지 확인하는 템플리트double(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4


를 인라인하는이 방법 has_foo의 템플릿 전화로 is_supported. 내가 원하는 것은 다음과 같은 것을 호출하는 것 std::cout << is_supported<magic.foo(), struct1>::value << std::endl;입니다. 그 이유는 has_foo함수를 확인하기 전에 확인하려는 각 다른 함수 서명에 대해 를 정의하고 싶습니까?
CJCombrink

2

이 솔루션은 어떻습니까?

#include <type_traits>

template <typename U, typename = void> struct hasToString : std::false_type { };

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

모호한 toString대로 오버로드되면 실패 &U::toString합니다.
Yakk-Adam Nevraumont

@ 야크 캐스트는이 문제를 해결할 수 있다고 생각합니다.
user1095108 2016 년

2

여기에는 많은 답변이 있지만 최신 c ++ 기능 (c ++ 98 기능 만 사용)을 사용하지 않고 실제 메서드 확인 순서 를 수행하는 버전을 찾지 못했습니다 .
참고 :이 버전은 vc ++ 2013, g ++ 5.2.0 및 온라인 컴파일러에서 테스트되어 작동합니다.

그래서 sizeof () 만 사용하는 버전을 생각해 냈습니다.

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

라이브 데모 (확장 된 리턴 유형 확인 및 vc ++ 2010 해결 방법 포함) : http://cpp.sh/5b2vs

내가 직접 생각해 낸 소스는 없습니다.

g ++ 컴파일러에서 라이브 데모를 실행할 때 배열 크기 0이 허용됩니다. 즉, 사용 된 static_assert가 실패하더라도 컴파일러 오류를 트리거하지 않습니다.
일반적으로 사용되는 해결 방법은 매크로의 'typedef'를 'extern'으로 바꾸는 것입니다.


아니요, 그러나 나는 그것을 스스로 선언하고 rvalue를 사용하지 않습니다 (내 코드의 상단을보십시오). 또는 자신을 설득하고 C ++ 98 모드에서 라이브 데모를 시도해 볼 수 있습니다. 추신 : static_assert도 c ++ 98이 아니지만 해결 방법이 있습니다 (실시간 데모)
user3296587

아! 그것을 놓쳤다. :-)
Ian Ni-Lewis

정적 어설 션이 작동하지 않습니다. 0 대신 배열 크기 -1을 사용해야합니다 (put put static_assert(false);). CRTP와 관련하여 이것을 사용하여 파생 클래스에 특정 기능이 있는지 여부를 확인하려고합니다. 작동하지 않는 것으로 나타 났지만 어설 션은 항상 전달되었습니다. 나는 그 머리카락을 잃었다.
돼지

g ++을 사용한다고 가정합니다. gcc / g ++에는 크기가 0 인 배열을 허용하는 확장 기능이 있습니다 ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587

연산자에 과부하가 걸리지 않도록 이것을 다시 작성할 수 있습니까? 예를 들어 다른 운영자를 선택 하시겠습니까? 또한 has_awesome_member 이외의 다른 이름으로 네임 스페이스를 오염시키지 마십시오.
einpoklum

1

다음은 템플릿 멤버 함수를 포함하여 기본 인수가있는 임의의 arity로 가능한 모든 멤버 함수 오버로드를 처리하는 내 버전입니다. 주어진 arg 유형으로 멤버 클래스를 멤버 클래스로 호출 할 때 상호 배타적 인 3 가지 시나리오를 구분합니다. (1) 유효하거나 (2) 모호하거나 (3) 실행 불가능합니다. 사용법 예 :

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

이제 다음과 같이 사용할 수 있습니다.

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

다음은 c ++ 11로 작성된 코드이지만, 약간의 조정을 통해 확장명이있는 비 c ++ 11 (예 : gcc)로 쉽게 이식 할 수 있습니다. HAS_MEM 매크로를 자신의 것으로 바꿀 수 있습니다.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif


1

C ++ 14의 모든 메타 프로그래밍을 건너 뛰고 Fit 라이브러리 fit::conditional에서 이를 사용하여 작성할 수 있습니다.

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

람다에서 직접 함수를 만들 수도 있습니다.

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

그러나 일반 람다를 지원하지 않는 컴파일러를 사용하는 경우 별도의 함수 객체를 작성해야합니다.

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);

1
fit표준 이외의 라이브러리 나 다른 라이브러리 에 의존하지 않아도되도록 작성하는 것이 얼마나 쉬운 가요?
einpoklum

1

C ++ 20을 사용하면 다음을 작성할 수 있습니다.

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

다음은 작업 코드의 예입니다.

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptr로 호출 할 때받는 int함수보다 우선 순위가있는 추가 인수를받는 함수를 활성화합니다 .long0

true함수가 구현되면 반환 하는 함수에 대해 동일한 원칙을 사용할 수 있습니다 .

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}

0

나는 비슷한 문제가 있었다.

소수의 기본 클래스에서 파생 될 수있는 템플릿 클래스로, 일부는 특정 멤버가 있고 다른 클래스는 그렇지 않습니다.

"typeof"(Nicola Bonelli 's) 답변과 비슷하게 해결되었지만 decltype을 사용하여 MSVS에서 올바르게 컴파일되고 실행됩니다.

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

0

C ++ 17에서 그것을하는 또 다른 방법 (boost : hana에서 영감을 얻음).

한 번 쓰고 여러 번 사용하십시오. has_something<T>유형 특성 클래스가 필요하지 않습니다 .

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

6
"답변 설명이 필요하지 않습니다"... 정보를 개선하기 위해 정보에 설명을 추가하십시오. 감사.
YesThatIsMyName
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.