Підходи до функціонування SFINAE в C ++


40

Я активно використовую функцію SFINAE в проекті і не впевнений, чи є якісь відмінності між наступними двома підходами (крім стилю):

#include <cstdlib>
#include <type_traits>
#include <iostream>

template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
    std::cout << "method 1" << std::endl;
}

template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
    std::cout << "method 2" << std::endl;
}

int main()
{
    foo<int>();
    foo<double>();

    std::cout << "Done...";
    std::getchar();

    return EXIT_SUCCESS;
}

Вихід програми очікується:

method 1
method 2
Done...

Я бачив метод 2, який частіше застосовується в stackoverflow, але я вважаю за краще метод 1.

Чи існують обставини, коли ці два підходи відрізняються?


Як ви запускаєте цю програму? Він не компілюється для мене.
alter igel

@alter igel йому знадобиться компілятор C ++ 17. Я використовував MSVC 2019 для тестування цього прикладу, але я в основному працюю з Clang.
Кіт

Пов’язано: чому-я повинен уникати-stdenable-if-in-function-підписи та C ++ 20 вводить також нові способи з концепцією :-)
Jarod42

@ Jarod42 Концепти - це одна з найпотрібніших речей для мене з C ++ 20.
Вал каже:

Відповіді:


35

Я бачив метод 2, який частіше застосовується в stackoverflow, але я вважаю за краще метод 1.

Пропозиція: віддайте перевагу методу 2.

Обидва методи працюють з одинарними функціями. Проблема виникає, коли у вас більше функції, з тією ж підписом, і ви хочете включити лише одну функцію набору.

Припустимо, ви хочете включити foo(), версію 1, коли bar<T>()(зробіть вигляд, що це constexprфункція) true, і foo()версія 2, коли bar<T>()є false.

З

template <typename T, typename = std::enable_if_t<true == bar<T>()>>
void foo () // version 1
 { }

template <typename T, typename = std::enable_if_t<false == bar<T>()>>
void foo () // version 2
 { }

ви отримуєте помилку компіляції, оскільки у вас є неоднозначність: дві foo()функції з однаковою підписом (параметр шаблону за замовчуванням не змінює підпис).

Але наступне рішення

template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
void foo () // version 1
 { }

template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
void foo () // version 2
 { }

працює, тому що SFINAE змінюють підпис функцій.

Неспоріднене спостереження: існує також третій метод: включити / вимкнути тип повернення (за винятком конструкторів класу / структури, очевидно)

template <typename T>
std::enable_if_t<true == bar<T>()> foo () // version 1
 { }

template <typename T>
std::enable_if_t<false == bar<T>()> foo () // version 2
 { }

Як спосіб 2, метод 3 сумісний з вибором альтернативних функцій з однаковою підписом.


1
Дякую за чудове пояснення, я віддаю перевагу методам 2 і 3 відтепер :-)
keith

"параметр шаблону за замовчуванням не змінює підпис" - чим це відрізняється у вашому другому варіанті, який також використовує параметри шаблону за замовчуванням?
Ерік

1
@Eric - Непросто сказати ... Я вважаю, що інша відповідь пояснить це краще ... Якщо SFINAE включити / відключити аргумент шаблону за замовчуванням, foo()функція залишається доступною, коли ви викликаєте його з явним другим параметром шаблону ( foo<double, double>();виклик). І якщо вони залишаться доступними, існує неоднозначність з іншою версією. З методом 2, SFINAE включає / відключає другий аргумент, а не параметр за замовчуванням. Таким чином, ви не можете назвати це експлікацією параметра, тому що відбувається збій підстановки, який не дозволяє другий параметр. Тож версія недоступна, тому ніякої двозначності
max66

3
Спосіб 3 має додаткову перевагу, як правило, не просочується в ім'я символу. Варіант auto foo() -> std::enable_if_t<...>часто корисний, щоб уникнути приховування функції-підпису та дозволити використання функції-аргументів.
Deduplicator

@ max66: значить, ключовим моментом є те, що помилка підстановки у параметрі шаблону за замовчуванням не є помилкою, якщо параметр надається та не потрібен за замовчуванням?
Ерік

21

На додаток до відповіді max66 , ще одна причина віддати перевагу методу 2 полягає в тому, що за допомогою методу 1 ви можете (випадково) передати явний параметр типу як другий аргумент шаблону і повністю перемогти механізм SFINAE. Це може статися як помилка друку, помилки копіювання / вставки, або як недогляд у більшому механізмі шаблонів.

#include <cstdlib>
#include <type_traits>
#include <iostream>

// NOTE: foo should only accept T=int
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo(){
    std::cout << "method 1" << std::endl;
}

int main(){

    // works fine
    foo<int>();

    // ERROR: subsitution failure, as expected
    // foo<double>();

    // Oops! also works, even though T != int :(
    foo<double, double>();

    return 0;
}

Тут демонструють демо-версію


Гарна думка. Механізм може бути викрадений.
max66
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.