ฉันต้องการเข้าสู่การเขียนโปรแกรมเมตาเทมเพลตเพิ่มเติม ฉันรู้ว่า SFINAE ย่อมาจาก "ความล้มเหลวในการทดแทนไม่ใช่ข้อผิดพลาด" แต่ใครช่วยบอกวิธีใช้ SFINAE ให้ฉันดูหน่อยได้ไหม
ฉันต้องการเข้าสู่การเขียนโปรแกรมเมตาเทมเพลตเพิ่มเติม ฉันรู้ว่า SFINAE ย่อมาจาก "ความล้มเหลวในการทดแทนไม่ใช่ข้อผิดพลาด" แต่ใครช่วยบอกวิธีใช้ SFINAE ให้ฉันดูหน่อยได้ไหม
คำตอบ:
นี่เป็นตัวอย่างหนึ่ง ( จากที่นี่ ):
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
ได้รับการประเมินจะไม่สามารถแปลง 0 เป็นint int::*
เนื่องจาก int ไม่ใช่คลาสจึงไม่สามารถมีตัวชี้สมาชิกได้ หากไม่มี SFINAE คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์บางอย่างเช่น '0 ไม่สามารถแปลงเป็นตัวชี้สมาชิกสำหรับประเภทที่ไม่ใช่คลาส int' แต่จะใช้...
รูปแบบที่ส่งคืน Two ดังนั้นจึงประเมินเป็นเท็จ int ไม่ใช่ประเภทคลาส
...
แต่เป็นสิ่งint C::*
ที่ฉันไม่เคยเห็นและต้องไปดู พบคำตอบว่ามันคืออะไรและใช้ทำอะไรได้ที่นี่: stackoverflow.com/questions/670734/…
ฉันชอบใช้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]
หมายถึง: C
ตัวชี้ไปยังอาร์เรย์ที่มีประเภทองค์ประกอบถ่านและขนาด ถ้าC
เป็นเท็จ (0 ที่นี่) แสดงว่าเราได้ประเภทที่ไม่ถูกต้องchar(*)[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) { /* ... */ }
}
ในทางปฏิบัติฉันมักพบว่าความสามารถในการตรวจสอบเงื่อนไขเป็นความสามารถที่มีประโยชน์
M <= N ? 1 : -1
จะทำงานแทนได้
int foo[0]
เพียงแค่พยายาม ฉันไม่แปลกใจเลยว่ามันได้รับการสนับสนุนเนื่องจากช่วยให้เคล็ดลับ "struct ที่ลงท้ายด้วยอาร์เรย์ 0 ความยาว" มีประโยชน์มาก ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
error C2466: cannot allocate an array of constant size 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
}
การใช้สำนวนประเภท sink ที่เรียกว่าคุณสามารถทำการทดสอบโดยพลการในประเภทต่างๆเช่นการตรวจสอบว่ามีสมาชิกหรือไม่และสมาชิกนั้นเป็นประเภทใดประเภทหนึ่งหรือไม่
//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;
}
นี่คือตัวอย่างสด: http://ideone.com/dHhyHE ฉันเพิ่งเขียนหัวข้อทั้งหมดใน SFINAE และการจัดส่งแท็กในบล็อกของฉัน (ปลั๊กไร้ยางอาย แต่เกี่ยวข้อง) http://metaporky.blogspot.de/2014/08/ ส่วน-7-คงส่ง-function.html
หมายเหตุสำหรับ C ++ 14 มี std :: void_t ซึ่งโดยพื้นฐานแล้วเหมือนกับ TypeSink ของฉันที่นี่
TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>
ในที่หนึ่งและTypeSinkT<decltype(&T::bar)>
อีกที่หนึ่งหรือไม่? ยังเป็น&
สิ่งที่จำเป็นในstd::declval<T&>
?
TypeSink
C ++ 17 มีstd::void_t
:)
ไลบรารีenable_ifของ Boost มีอินเทอร์เฟซที่สะอาดตาสำหรับการใช้ SFINAE หนึ่งในตัวอย่างการใช้งานที่ฉันชอบอยู่ในไลบรารีBoost.Iterator SFINAE ใช้เพื่อเปิดใช้งานการแปลงประเภท iterator
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 {};
ตัวอย่างต่อไปนี้นำมาจาก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_t
และdetect
) พอเพียง นอกจากนี้ยังมีรายงาน (ดูN4502 ) ว่ามีประสิทธิภาพมากกว่า (เวลาคอมไพล์และการใช้หน่วยความจำคอมไพเลอร์) มากกว่าวิธีการก่อนหน้านี้
นี่คือตัวอย่างสดซึ่งรวมถึงการปรับแต่งการพกพาสำหรับ GCC ก่อน 5.1
นี่คืออีกตัวอย่างSFINAE (ตอนปลาย) ตามคำตอบของGreg Rogers :
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);
ด้วยวิธีนี้คุณสามารถตรวจสอบvalue
ค่าของเพื่อดูว่าT
เป็นคลาสหรือไม่:
int main(void) {
std::cout << IsClassT<std::string>::value << std::endl; // true
std::cout << IsClassT<int>::value << std::endl; // false
return 0;
}
int C::*
ในคำตอบของคุณหมายถึงอะไร? วิธีการอาจC::*
จะเป็นชื่อพารามิเตอร์หรือไม่?
int C::*
เป็นประเภทของตัวชี้ไปนั้นตัวแปรสมาชิกของint
C
นี่คือบทความดีๆของ SFINAE: บทนำเกี่ยวกับแนวคิด SFINAE ของ C ++: รวบรวมการวิปัสสนาตามเวลาของสมาชิกในชั้นเรียนเวลารวบรวมวิปัสสนาของสมาชิกชั้นเรียน
สรุปได้ดังต่อไปนี้:
/*
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
เป็นยูทิลิตี้ที่ให้ "การอ้างอิงปลอม" แก่คุณไปยังวัตถุประเภทที่ไม่สามารถสร้างได้ง่ายๆ declval
มีประโยชน์มากสำหรับโครงสร้าง SFINAE ของเรา
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';
}
ที่นี่ฉันกำลังใช้ฟังก์ชัน template มากเกินไป (ไม่ใช่ SFINAE โดยตรง) เพื่อตรวจสอบว่าตัวชี้เป็นฟังก์ชันหรือตัวชี้ระดับสมาชิก: ( เป็นไปได้ไหมที่จะแก้ไขตัวชี้ฟังก์ชันสมาชิก iostream cout / cerr ที่พิมพ์เป็น 1 หรือจริง? )
#include<iostream>
template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
return true;
}
template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
return true;
}
template<typename... Args>
constexpr bool is_function_pointer(Args...) {
return false;
}
struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}
int main(void) {
int* var;
std::cout << std::boolalpha;
std::cout << "0. " << is_function_pointer(var) << std::endl;
std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
return 0;
}
พิมพ์
0. false
1. true
2. true
3. true
4. true
ตามที่เป็นรหัสมันสามารถ (ขึ้นอยู่กับคอมไพเลอร์ "good" will) สร้าง run time call ไปยังฟังก์ชันซึ่งจะคืนค่า true หรือ false หากคุณต้องการบังคับis_function_pointer(var)
ให้ทำการประเมินในประเภทคอมไพล์ (ไม่มีการเรียกใช้ฟังก์ชันในขณะรัน) คุณสามารถใช้constexpr
เคล็ดลับตัวแปร:
constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;
ตามมาตรฐาน C ++ constexpr
รับประกันว่าตัวแปรทั้งหมดจะได้รับการประเมินในเวลาคอมไพล์ ( ความยาวการคำนวณของสตริง C ในเวลาคอมไพล์นี่คือ constexpr จริงหรือ? )
รหัสต่อไปนี้ใช้ SFINAE เพื่อให้คอมไพลเลอร์เลือกโอเวอร์โหลดโดยขึ้นอยู่กับว่าประเภทมีวิธีการบางอย่างหรือไม่:
#include <iostream>
template<typename T>
void do_something(const T& value, decltype(value.get_int()) = 0) {
std::cout << "Int: " << value.get_int() << std::endl;
}
template<typename T>
void do_something(const T& value, decltype(value.get_float()) = 0) {
std::cout << "Float: " << value.get_float() << std::endl;
}
struct FloatItem {
float get_float() const {
return 1.0f;
}
};
struct IntItem {
int get_int() const {
return -1;
}
};
struct UniversalItem : public IntItem, public FloatItem {};
int main() {
do_something(FloatItem{});
do_something(IntItem{});
// the following fails because template substitution
// leads to ambiguity
// do_something(UniversalItem{});
return 0;
}
เอาท์พุท:
ลอย: 1 Int: -1