특정 멤버 함수가 클래스에 정의되어 있는지 여부에 따라 동작을 변경하는 템플릿을 작성할 수 있습니까?
다음은 내가 쓰고 싶은 간단한 예입니다.
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
경우에 따라서, class T
한 toString()
정의하고 그것을 사용; 그렇지 않으면 그렇지 않습니다. 내가 모르는 마법의 부분은 “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;
}
int
및 long
매개 변수는 같은 이유로 여전히 있습니다. 배열 포인터는 사용될 수있는 컨텍스트를 제공하는 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 {
...
}
}
그러나 toString
if 분기에서 해당 함수를 호출 할 수는 없습니다 . 컴파일러는 두 가지 모두에서 유효성을 검사하므로 함수가 존재하지 않는 경우 실패합니다. 한 가지 방법은 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_valid
C ++ 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
포함 합니다 .true
T
std::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;
}