Вопрос

Я хочу больше заниматься шаблонным метапрограммированием.Я знаю, что 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'.Вместо этого он просто использует ... форма, которая возвращает Два и, таким образом, принимает значение 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 */
}

Это может быть весьма полезно.Например, я использовал его, чтобы проверить, не превышает ли размер списка инициализаторов, собранного с помощью оператора comma, фиксированного размера

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

Список принимается только тогда, когда M меньше N, что означает, что список инициализатора содержит не слишком много элементов.

Синтаксис char(*)[C] означает:Указатель на массив с типом элемента char и размером 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) { /* ... */ }
}

На практике я часто нахожу способность проверять условия полезной способностью.

В 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
}

Используя так называемую идиому type 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/part-7-static-dispatch-function.html

Обратите внимание, что начиная с C ++ 14 существует std::void_t, который по сути такой же, как мой TypeSink здесь.

Повышение разрешение_if библиотека предлагает приятный чистый интерфейс для использования SFINAE.Один из моих любимых примеров использования находится в Повышение.Итератор библиотека.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 pre 5.1.

Вот еще один (поздний) СФИНАЭ пример, основанный на Грег Роджерс'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;
}

Вот одна хорошая статья 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';
}

Новый блог существует с момента последнего ответа на эту тему.

Он свободно владеет C ++: http://fluentcpp.com/

Существует множество примеров для исследования "SFINAE".

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top