C ++ SFINAE 예제?
더 많은 템플릿 메타 프로그래밍에 들어가고 싶습니다. SFINAE는 "대체 실패는 오류가 아닙니다"를 의미합니다. 그러나 누군가 SFINAE를 잘 사용한다는 것을 보여줄 수 있습니까?
여기에 하나의 예가 있습니다 ( here ).
template<typename T>
class IsClassT {
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename C> static One test(int C::*);
// Will be chosen if T is anything except a class.
template<typename C> static Two test(...);
public:
enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
enum { No = !Yes };
};
IsClassT<int>::Yes
평가 되면 int int::*
int가 클래스가 아니므 로 0을 변환 할 수 없으므로 멤버 포인터를 가질 수 없습니다. SFINAE가 존재하지 않으면 컴파일러 오류가 발생합니다. '0은 비 클래스 유형 int의 멤버 포인터로 변환 할 수 없습니다.' 대신 ...
2를 반환 하는 형식을 사용 하므로 false로 평가됩니다. int는 클래스 유형이 아닙니다.
SFINAE
부울 조건을 확인 하는 데 사용하는 것이 좋습니다.
template<int I> void div(char(*)[I % 2 == 0] = 0) {
/* this is taken when I is even */
}
template<int I> void div(char(*)[I % 2 == 1] = 0) {
/* this is taken when I is odd */
}
꽤 유용 할 수 있습니다. 예를 들어, 연산자 쉼표를 사용하여 수집 한 이니셜 라이저 목록이 고정 크기 이상인지 확인하는 데 사용했습니다.
template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}
M이 N보다 작은 경우에만 목록이 허용되므로 초기화 목록에 요소가 너무 많지 않습니다.
구문 char(*)[C]
은 다음을 의미합니다. 요소 유형이 char 및 size 인 배열의 포인터 C
. 경우 C
(여기서 0) 거짓, 우리는 잘못된 유형 얻을 char(*)[0]
크기가 0 인 배열, 포인터를 : SFINAE 템플릿은 다음 무시됩니다 있도록한다.
로 표현하면 boost::enable_if
다음과 같습니다.
template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> const& i,
typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}
실제로, 나는 종종 조건을 점검하는 능력이 유용한 능력을 발견합니다.
C ++ 11에서 SFINAE 테스트는 훨씬 더 예쁘게되었습니다. 다음은 일반적인 용도의 몇 가지 예입니다.
특성에 따라 기능 과부하 선택
template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
//integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
//floating point version
}
Using a so called type sink idiom you can do pretty arbitrary tests on a type like checking if it has a member and if that member is of a certain type
//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;
//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};
struct S{
int bar;
};
struct K{
};
template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
std::cout << "has bar" << std::endl;
}
void print(...){
std::cout << "no bar" << std::endl;
}
int main(){
print(S{});
print(K{});
std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}
Here is a live example: http://ideone.com/dHhyHE I also recently wrote a whole section on SFINAE and tag dispatch in my blog (shameless plug but relevant) http://metaporky.blogspot.de/2014/08/part-7-static-dispatch-function.html
Note as of C++14 there is a std::void_t which is essentially the same as my TypeSink here.
Boost's enable_if library offers a nice clean interface for using SFINAE. One of my favorite usage examples is in the Boost.Iterator library. SFINAE is used to enable iterator type conversions.
C++17 will probably provide a generic means to query for features. See N4502 for details, but as a self-contained example consider the following.
This part is the constant part, put it in a header.
// 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 {};
The following example, taken from N4502, shows the usage:
// 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>;
Compared to the other implementations, this one is fairly simple: a reduced set of tools (void_t
and detect
) suffices. Besides, it was reported (see N4502) that it is measurably more efficient (compile-time and compiler memory consumption) than previous approaches.
Here is a live example, which includes portability tweaks for GCC pre 5.1.
Here's another (late) SFINAE example, based on Greg Rogers's answer:
template<typename T>
class IsClassT {
template<typename C> static bool test(int C::*) {return true;}
template<typename C> static bool test(...) {return false;}
public:
static bool value;
};
template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);
In this way, you can check the value
's value to see whether T
is a class or not:
int main(void) {
std::cout << IsClassT<std::string>::value << std::endl; // true
std::cout << IsClassT<int>::value << std::endl; // false
return 0;
}
Here is one good article of SFINAE: An introduction to C++'s SFINAE concept: compile-time introspection of a class member.
Summary it as following:
/*
The compiler will try this overload since it's less generic than the variadic.
T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
It simply tries the next overload.
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }
// The sink-hole.
void f(...) { }
f(1); // Calls void f(...) { }
template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.
template<class T> // A specialisation used if the expression is true.
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.
template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
return obj.serialize();
}
template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
return to_string(obj);
}
declval
is an utility that gives you a "fake reference" to an object of a type that couldn't be easily construct. declval
is really handy for our SFINAE constructions.
struct Default {
int foo() const {return 1;}
};
struct NonDefault {
NonDefault(const NonDefault&) {}
int foo() const {return 1;}
};
int main()
{
decltype(Default().foo()) n1 = 1; // int n1
// decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
std::cout << "n2 = " << n2 << '\n';
}
A new blog exists since the last answer to this thread.
It is Fluent C++: http://fluentcpp.com/
There are plenty of examples for a research on "SFINAE".
참고URL : https://stackoverflow.com/questions/982808/c-sfinae-examples
'developer tip' 카테고리의 다른 글
2 차원 배열에 대한 포인터 만들기 (0) | 2020.08.04 |
---|---|
Delphi 언어 기능 및 소개 / 비추천 된 버전 목록 (0) | 2020.08.04 |
HTML“overflow : auto”와“overflow : scroll”의 차이점 (0) | 2020.08.04 |
Subversion 상태 기호 "~"는 무엇을 의미합니까? (0) | 2020.08.04 |
Maven의 샘플 settings.xml (0) | 2020.08.04 |