C ++ SFINAE примеры? - PullRequest
       66

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

105 голосов
/ 11 июня 2009

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

Ответы [ 8 ]

85 голосов
/ 13 июня 2009

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

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

62 голосов
/ 11 июня 2009

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

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 не является типом класса.

11 голосов
/ 11 августа 2014

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

8 голосов
/ 11 июня 2009
Библиотека

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

3 голосов
/ 16 июня 2015

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.

2 голосов
/ 28 февраля 2015

Вот еще один (поздний) SFINAE пример, основанный на Греге Роджерсе * ответ :

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;
}
1 голос
/ 25 декабря 2015

Вот одна хорошая статья 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';
}
0 голосов
/ 19 апреля 2019

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

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

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

...