Как упростить сложный синтаксис SFINAE, в pre-С++ 11, С++ 11, 14 и 17?

Этот вопрос был вдохновлен этим ответом. Интересно, какие/были лучшие способы упростить его в данных стандартах. Один, который я знаю и использовал лично/по-прежнему, поскольку С++ 14 - это макрос REQUIRES(x):

С определением:

template<long N>
struct requires_enum
{
    enum class type
    {
        none,
        all
    };
};

#define REQUIRES(...) requires_enum<__LINE__>::type = \
                      requires_enum<__LINE__>::type::none, \
                      bool PrivateBool = true, \
                      typename std::enable_if<PrivateBool && (__VA_ARGS__), int>::type = 0

И используйте, если даже для не-templated вызовов функций:

template<REQUIRES(sizeof(int)==4)>
int fun() {return 0;}

int main()
{ 
    fun(); //only if sizeof(int)==4
}

Оригинальный REQUIRES, я использую это post.

Каковы другие хорошие методы?


Некоторые примеры SFINAE, которые требуют некоторого времени или долгое время для понимания для читателя, который только начал приключение с SFINAE:

Pre-С++ 11 Пример SFINAE (Источник):

template <typename T>
struct has_typedef_foobar {
    // Types "yes" and "no" are guaranteed to have different sizes,
    // specifically sizeof(yes) == 1 and sizeof(no) == 2.
    typedef char yes[1];
    typedef char no[2];

    template <typename C>
    static yes& test(typename C::foobar*);

    template <typename>
    static no& test(...);

    // If the "sizeof" of the result of calling test<T>(nullptr) is equal to sizeof(yes),
    // the first overload worked and T has a nested type named foobar.
    static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};

Ответ 1

Если вы работаете с С++ 11 (код примера содержит std::enable_if, поэтому, я думаю, это так) или последовательная ревизия, я бы использовал static_assert в этом случае:

int fun() {
    static_assert(sizeof(int)==4, "!");
    return 0;
}

int main() {
    fun();
}

У вас нет набора функций, из которых можно выбрать рабочий.
Как я уже сказал, это скорее сбой замены, всегда ошибка, чем сбой замены, это не ошибка.
То, что вы хотите, это триггер компиляции, а static_assert - с нежными сообщениями об ошибках.

Конечно, это также намного легче читать, чем сложное выражение sfinae тоже!


Если вы хотите выбрать одну из двух функций, и вы не хотите использовать шаблонные механизмы или макросы, не забывайте, что перегрузка является частью языка (рабочий пример до С++ 11):

#include <iostream>

template<bool> struct tag {};
int fun(tag<true>) { return 0; } 
int fun(tag<false>) { return 1; }
int fun() { return fun(tag<sizeof(int) == 4>()); }

int main() {
    std::cout << fun() << std::endl;
}

Это можно легко распространить на случаи, когда функции больше двух:

#include <iostream>

template<int> struct tag {};
int fun(tag<0>) { return 0; }
int fun(tag<1>) { return 1; }
int fun(tag<2>) { return 2; }

int fun(bool b) {
    if(b) { return fun(tag<0>()); }
    else { return fun(tag<(sizeof(int) == 4) ? 1 : 2>());
}

int main() {
    std::cout << fun(false) << std::endl;
}

Вы можете поместить эти функции в анонимное пространство имен и уйти с ними.


Конечно, обратите внимание также, что в pre-С++ 11 нам было разрешено писать enable_if и все другие вещи из type_traits для себя.
В качестве примера:

template<bool b, typename = void>
struct enable_if { };

template<typename T>
struct enable_if<true, T> { typedef T type; };

Ответ 2

Включить, если довольно легко реализовать. Взгляните на эту реализацию:

template<bool b, typename T = void>
struct enable_if {
    typedef T type;
};

template<typename T>
struct enable_if<false, T> {};

В С++ 11 я обычно объявляю некоторые псевдонимы. Поскольку вы застряли в эпоху до С++ 11, вы можете сделать это вместо:

template<bool b>
struct enable_if_parameter : enable_if<b, int*> {};

Затем вы можете использовать структуру следующим образом:

template<typename T, typename enable_if_parameter<(sizeof(T) >= 0)>::type = 0>
void someFunc() {
    // ...
}

Если вы можете позволить себе С++ 17, вы можете сделать это:

template<bool b>
using enable_if_parameter = std::enable_if_t<b, int*>;

И затем сделайте следующее:

template<typename T, enable_if_parameter<std::is_same_v<T, int>> = 0>

Мне также нравится idt void_t для создания новых черт типа:

template<typename T, typename = void>
struct has_callme : std::false_type {};

template<typename T>
struct has_callme<T, void_t<decltype(std::declval<T>().callme())>> : std::true_type {};

Ответ 3

В С++ 03 вы просто пишете enable_if самостоятельно. Он не требует функций С++ 11.

Причина, по которой вы используете разные методы, заключается в том, что компиляторы pre-С++ 11 иногда имеют смешное определение того, что SFINAE и что должно быть ошибкой. MSVC - это текущий главный компилятор, который все еще (в пред-С++ 17-й эре) имеет очень изворотливое определение того, что является действительным SFINAE из-за их проблем с SFTINAE decltype.


В С++ 11 вы должны написать void_t и enable_if_t, чтобы упростить ваш материал SFINAE.

Вы также должны написать это:

namespace details {
  template<template<class...>class Z, class always_void, class...Ts>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

который позволяет писать черты и спрашивать, действительно ли что-то действительно (вы можете вызвать метод? Создайте псевдоним, который выполняет decltype в вызове, а затем спросите, можете ли вы применить этот тип к псевдониму). Это все еще необходимо в С++ 14 и 17, но С++ 20, вероятно, получит is_detected, который выполняет аналогичную цель.

Итак can_print:

template<class T>using print_result = decltype(
  std::declval<std::ostream&>() << std::declval<T>()
);
template<class T>using can_print = can_apply< print_result, T >;

он либо правдивый, либо фальшивый, в зависимости от того, работает ли << с потоком с ним.

В С++ 14 вы можете начать использовать метапрограммирование в стиле hana, чтобы создать lambdas, которые выполняют манипуляции типа для вас. В С++ 17 они становятся constexpr, что избавляет от некоторых проблем.


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

Итак, это:

template<REQUIRES(sizeof(int)==4)>
int fun() {
  // code that is ill-formed if `int` does not have size 4
}

скорее всего будет компилироваться и запускаться и "делать то, что вы хотите", но на самом деле это плохо сформированная программа, когда sizeof(int) - 8.

То же самое можно сказать и об использовании этого метода для отключения методов в классах на основе аргументов шаблона класса. Станард неясен в этом вопросе, поэтому я избегаю этого.

Макрос REQUIRES пытается скрыть, как он работает за магией, но слишком легко перешагнуть через линию и создать плохо сформированную программу. Скрытие волшебства, когда детали магии приводят к плохому формированию вашего кода, не является хорошим планом.


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

template<std::size_t N>
struct overload_priority : overload_priority<N-1> {};
template<>
struct overload_priority<0> {};

Теперь вы можете передать overload_priority<50>{} в набор функций, и тот, который имеет самый высокий overload_priority<?> в этом слоте, будет предпочтительнее.

template<class T>struct tag_t{using type=T;};

namespace details {
  inline int fun( tag_t<int[4]> ) { return 0; }
  inline int fun( tag_t<int[8]> ) { return 1; }
}
int fun() { return details::fun( tag_t<int[sizeof(int)]>{} ); }

просто отправлен на другую функцию в зависимости от размера int.

Обе команды fun перегружаются, компилируются и проверяются, поэтому вы не сталкиваетесь с проблемой неработающей программы.

Функция, действительность которой не является функцией ее аргументов шаблона, небезопасна для использования в С++. Вы должны использовать другую тактику. Машины, которые делают это проще, просто облегчают работу с плохо сформированными программами.