Я хочу потрапити на більше мета-програмування шаблонів. Я знаю, що 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, і, таким чином, оцінює значення false, 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
значення false (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]
. Я не здивований, що його підтримують, оскільки це дозволяє дуже корисний трюк "структура, що закінчується масивом 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
}
Використовуючи так звану ідіому мийки типу, ви можете робити досить довільні тести типу типу, наприклад, перевірити, чи є у нього член і чи є цей член певного типу
//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/ part-7-static-dispatch-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 використовується для включення перетворень типу ітератора.
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 приклад, заснований на Грег Роджерс «S відповідь :
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';
}
Тут я використовую функцію перевантаження функції шаблону (не безпосередньо 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
Що стосується коду, він може (залежно від компілятора "добрий" буде) викликати виклик часу виконання функції, яка поверне справжню або помилкову. Якщо ви хочете змусити is_function_pointer(var)
оцінювати тип компіляції (жодні виклики функцій, які виконуються під час виконання), ви можете використовувати constexpr
змінний трюк:
constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;
За стандартом 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