В C++ примеры SFINAE?



Я хочу попасть в более шаблон мета-программирования. Я знаю, что SFINAE означает " ошибка замены не является ошибкой.- Но может ли кто-нибудь показать мне хорошее применение СФИНЕ?

667   7  

7 ответов:

вот один из примеров (отсюда):

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) { /* ... */ }
}

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

повышение по enable_if библиотека предлагает хороший чистый интерфейс для использования SFINAE. Один из моих любимых примеров использования в импульс.Итератор библиотека. SFINAE используется для включения преобразования типов итераторов.

в 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 и Tag dispatch в моем блоге (бесстыдный плагин, но релевантный) http://metaporky.blogspot.de/2014/08/part-7-static-dispatch-function.html

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

вот еще (поздно) 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 ' s значение, чтобы увидеть ли T - это класс или нет:

int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}

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.

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

Comments

    Ничего не найдено.