Различия в использовании std::enable_if и универсальных ссылок

Я пытаюсь понять универсальные ссылки и std::enable_if лучше, но я немного застрял в том, что здесь происходит в моем коде.

Во-первых, я заметил, что люди, кажется, используют std::enable_if двумя разными способами:

  1. template<typename T, std::enable_if<condition, T>::type* = nullptr> или что-то похожее на это.

  2. template<typename T> std::enable_if_t<condition, T> myfunc() {...} или что-то похожее на это.

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

Я также застрял на универсальных ссылках при использовании enable_if, Вот мой код и результаты, которые я получаю. Обратите внимание, что я использую код печати типа Говарда Хиннанта из " Можно ли напечатать тип переменной в стандартном C++?", Который я опущу здесь для краткости.

В любом случае, функция conditionless Кажется, работает нормально со всем.

Я очень смущен is_integral а также decay, который вы можете увидеть в начале main, Я получаю вывод:

true: unsigned long false: unsigned long false: unsigned long false: unsigned long

и я понятия не имею, почему последние три являются ложными.

Тогда у меня есть проблемы (отмечены 1 и 2 в источнике ниже), где при использовании enable_if любым из двух упомянутых выше способов они отказываются компилироваться, принимая lvalue целочисленного типа или типа с плавающей запятой.

Заголовки и тип печатного кода опущены для краткости:

template<typename T>
void conditionless(T&& val) {
    std::cout << "conditionless(" << val << ")\n";
}

template<typename T, typename std::enable_if<std::is_integral_v<T>, T>::type* = nullptr>
void outputIntType(T&& val) {
    std::cout << "outputIntType(" << val << ")\n";
}

template<typename T>
typename std::enable_if_t<std::is_floating_point_v<T>>
outputFloatType(T&& val) {
    std::cout << "outputFloatType(" << val << ")\n";
}

int main() {

    size_t sz = 1;
    size_t &ref = sz;

    // All of these report as having type "unsigned long", but for some reason, the first reports true for is_integral, and
    // the other three report false.
    std::cout << std::boolalpha << std::is_integral_v<decltype(sz)> << ": " << type_name<decltype(sz)>() << '\n';
    std::cout << std::boolalpha << std::is_integral_v<std::decay<decltype(sz)>> << ": " << type_name<std::decay<decltype(sz)>::type>() << '\n';
    std::cout << std::boolalpha << std::is_integral_v<decltype(ref)> << ": " << type_name<decltype(sz)>() << '\n';
    std::cout << std::boolalpha << std::is_integral_v<std::decay<decltype(ref)>> << ": " << type_name<std::decay<decltype(ref)>::type>() <<'\n';

    //  This works fine.
    conditionless(sz);
    conditionless(2UL);
    conditionless(2L + 1);

    // ******* 1 *******
    // This fails and claims no matching function call to outputIntType(size_t&)
    // template argument deduction / substitution failed:
    // error: no type named 'type' in 'struct std::enable_if<false, long unisgned int&>'
    // I'm particularly confused about why the is_integral evaluates to false.
    //outputIntType(sz); 

    // These work fine.
    outputIntType(2UL);
    outputIntType(2L + 1);

    double pi = 3.1415926535;

    // These work fine.
    conditionless(pi);
    conditionless(2 * pi);
    conditionless(0.00000001);

    // ******* 2 *******
    // This fails as well:
    // main.cpp: In function 'int main()':
    // error: no matching function for call to 'outputFloatType(double&)'
    // note: candidate: 'template<class T> std::enable_if_t<is_floating_point_v<T> > outputFloatType(T&&)'
    // template argument deduction/substitution failed:
    // outputFloatType(pi);

    // These work fine.
    outputFloatType(2 * pi);
    outputFloatType(0.00000001);
}

Любое понимание того, что кто-либо может дать мне о двух разных enable_if и почему мой код с enable_if отказывается принять lvalues ​​будет принята с благодарностью.

1 ответ

Решение

Я пытаюсь понять универсальные ссылки

Использование этого термина не рекомендуется. Официальный термин "пересылка ссылок".


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

Все enable_if_t<B, T> делает это оценить T если B == trueв противном случае выдает неверный код. Неверный код, созданный во время подстановки, не приводит к ошибке компиляции (SFINAE).

Не важно где enable_if_t появляется, пока на него влияет шаг замещения (например, может быть в типе возврата, списке параметров, списке параметров шаблона,...).


и я понятия не имею, почему последние три являются ложными.

Вы забыли доступ ::type в вашем std::decay преобразование. Вы сравниваете саму черту, а не ее результат.


они отказываются компилироваться, принимая lvalue целочисленного типа или типа с плавающей запятой.

В стандарте существует специальное правило, касающееся удержания пересылочных ссылок. При заданном параметре переадресации T&&, T будет выведен как ссылка lvalue, если функция вызывается с lvalue.

Вы должны принять это во внимание в своих чертах:

typename std::enable_if_t<std::is_floating_point_v<std::remove_reference_t<T>>>
Другие вопросы по тегам