클래스 멤버 함수가 있는지 템플릿으로 검사 했습니까? 간단한 예입니다. template<class T> std::string optionalToString(T*

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

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

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”부분입니다.



답변

예, 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으로 테스트했습니다. 다른 컴파일러를 실행하는 다른 플랫폼으로 이식 가능한지 모르겠습니다.


답변

이 질문은 오래되었지만 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데 사용됩니다.


답변

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멤버 함수 포인터 유형으로 사용하십시오!).


답변

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.


답변

이 질문은 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에게 전달되므로 내 답변이 도움이된다면 투표하십시오. 🙂


답변

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


답변

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

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