Saya ingin masuk ke lebih banyak meta-pemrograman template. Saya tahu bahwa SFINAE adalah singkatan dari "kegagalan substitusi bukanlah kesalahan." Tetapi dapatkah seseorang menunjukkan kepada saya penggunaan yang baik untuk SFINAE?
Saya ingin masuk ke lebih banyak meta-pemrograman template. Saya tahu bahwa SFINAE adalah singkatan dari "kegagalan substitusi bukanlah kesalahan." Tetapi dapatkah seseorang menunjukkan kepada saya penggunaan yang baik untuk SFINAE?
Jawaban:
Berikut salah satu contohnya ( dari sini ):
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 };
};
Saat IsClassT<int>::Yes
dievaluasi, 0 tidak bisa diubah int int::*
karena int bukan kelas, jadi tidak bisa memiliki penunjuk anggota. Jika SFINAE tidak ada, maka Anda akan mendapatkan error compiler, seperti '0 tidak dapat diubah menjadi pointer anggota untuk non-class type int'. Sebaliknya, ini hanya menggunakan ...
formulir yang mengembalikan Dua, dan dengan demikian mengevaluasi ke salah, int bukanlah tipe kelas.
...
, melainkan int C::*
, yang belum pernah saya lihat dan harus saya cari.
Saya suka menggunakan SFINAE
untuk memeriksa kondisi boolean.
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 */
}
Ini bisa sangat berguna. Misalnya, saya menggunakannya untuk memeriksa apakah daftar penginisialisasi yang dikumpulkan menggunakan koma operator tidak lebih dari ukuran tetap
template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}
Daftar hanya diterima jika M lebih kecil dari N, yang berarti daftar penginisialisasi tidak terlalu banyak elemen.
Sintaksnya char(*)[C]
berarti: Pointer ke array dengan tipe elemen char dan size C
. Jika C
salah (0 di sini), maka kita mendapatkan tipe yang tidak valid char(*)[0]
, penunjuk ke array berukuran nol: SFINAE membuatnya sehingga template akan diabaikan kemudian.
Dinyatakan dengan boost::enable_if
, terlihat seperti ini
template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> const& i,
typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}
Dalam prakteknya, saya sering menemukan kemampuan untuk memeriksa kondisi sebagai kemampuan yang berguna.
M <= N ? 1 : -1
bisa berhasil.
int foo[0]
. Saya tidak heran ini didukung, karena memungkinkan trik yang sangat berguna "struct diakhiri dengan 0-length array" ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html ).
error C2466: cannot allocate an array of constant size 0
Dalam C ++ 11, pengujian SFINAE menjadi jauh lebih cantik. Berikut beberapa contoh penggunaan umum:
Pilih fungsi yang kelebihan beban bergantung pada sifatnya
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
}
Menggunakan apa yang disebut idiom tipe sink Anda dapat melakukan tes yang cukup sewenang-wenang pada suatu tipe seperti memeriksa apakah ia memiliki anggota dan jika anggota itu dari tipe tertentu
//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;
}
Berikut ini adalah contoh langsungnya: http://ideone.com/dHhyHE Saya juga baru-baru ini menulis seluruh bagian di SFINAE dan pengiriman tag di blog saya (steker tidak tahu malu tetapi relevan) http://metaporky.blogspot.de/2014/08/ bagian-7-static-dispatch-function.html
Perhatikan pada C ++ 14 ada std :: void_t yang pada dasarnya sama dengan TypeSink saya di sini.
TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>
di satu tempat dan kemudian TypeSinkT<decltype(&T::bar)>
di tempat lain? Juga apakah &
perlu di std::declval<T&>
?
TypeSink
, C ++ 17 memiliki std::void_t
:)
Pustaka enable_if Boost menawarkan antarmuka bersih yang bagus untuk menggunakan SFINAE. Salah satu contoh penggunaan favorit saya ada di pustaka Boost.Iterator . SFINAE digunakan untuk mengaktifkan konversi jenis iterator.
C ++ 17 mungkin akan menyediakan cara umum untuk meminta fitur. Lihat N4502 untuk detailnya, tetapi sebagai contoh mandiri pertimbangkan hal berikut.
Bagian ini adalah bagian yang konstan, taruh di 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 {};
Contoh berikut, diambil dari N4502 , menunjukkan penggunaan:
// 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>;
Dibandingkan dengan implementasi lainnya, yang satu ini cukup sederhana: seperangkat alat ( void_t
dan detect
) sudah cukup. Selain itu, telah dilaporkan (lihat N4502 ) bahwa ini jauh lebih efisien (waktu kompilasi dan konsumsi memori kompilator) daripada pendekatan sebelumnya.
Berikut adalah contoh langsung , yang menyertakan penyesuaian portabilitas untuk GCC pra 5.1.
Berikut lain (akhir) SFINAE contoh, berdasarkan Greg Rogers 's jawabannya :
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);
Dengan cara ini, Anda dapat memeriksa value
nilai untuk melihat apakah T
kelas atau bukan:
int main(void) {
std::cout << IsClassT<std::string>::value << std::endl; // true
std::cout << IsClassT<int>::value << std::endl; // false
return 0;
}
int C::*
dalam jawaban Anda? Bagaimana bisa C::*
menjadi nama parameter?
int C::*
adalah tipe penunjuk ke int
variabel anggota C
.
Berikut adalah satu artikel bagus dari SFINAE: Pengantar konsep SFINAE C ++: introspeksi waktu kompilasi anggota kelas .
Rangkumlah sebagai berikut:
/*
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
adalah utilitas yang memberi Anda "referensi palsu" ke objek dari jenis yang tidak dapat dibuat dengan mudah. declval
sangat berguna untuk konstruksi SFINAE kami.
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';
}
Di sini, saya menggunakan template function overloading (tidak secara langsung SFINAE) untuk menentukan apakah sebuah pointer adalah sebuah fungsi atau pointer kelas anggota: ( Apakah mungkin untuk memperbaiki pointer fungsi anggota iostream cout / cerr yang dicetak sebagai 1 atau benar? )
#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;
}
Cetakan
0. false
1. true
2. true
3. true
4. true
Seperti kodenya, ia bisa (tergantung pada kompiler "good" will) menghasilkan panggilan run time ke fungsi yang akan mengembalikan true atau false. Jika Anda ingin memaksa is_function_pointer(var)
untuk mengevaluasi pada jenis kompilasi (tidak ada panggilan fungsi yang dilakukan pada waktu proses), Anda dapat menggunakan constexpr
trik variabel:
constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;
Dengan standar C ++, semua constexpr
variabel dijamin akan dievaluasi pada waktu kompilasi ( Menghitung panjang string C pada waktu kompilasi. Apakah ini benar-benar sebuah konsteks? ).
Kode berikut menggunakan SFINAE untuk membiarkan compiler memilih kelebihan beban berdasarkan apakah suatu jenis memiliki metode tertentu atau tidak:
#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;
}
Keluaran:
Mengapung: 1 Int: -1