web-dev-qa-db-fra.com

Approches pour fonctionner SFINAE en C ++

J'utilise beaucoup la fonction SFINAE dans un projet et je ne sais pas s'il existe des différences entre les deux approches suivantes (autres que le style):

#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;
}

La sortie du programme est comme prévu:

method 1
method 2
Done...

J'ai vu la méthode 2 utilisée plus souvent dans stackoverflow, mais je préfère la méthode 1.

Y a-t-il des circonstances où ces deux approches diffèrent?

40
keith

J'ai vu la méthode 2 utilisée plus souvent dans stackoverflow, mais je préfère la méthode 1.

Suggestion: préférez la méthode 2.

Les deux méthodes fonctionnent avec des fonctions uniques. Le problème se pose lorsque vous avez plus d'une fonction, avec la même signature, et que vous ne souhaitez activer qu'une seule fonction de l'ensemble.

Supposons que vous souhaitiez activer foo(), version 1, lorsque bar<T>() (prétendre qu'il s'agit d'une fonction constexpr) est true et foo(), version 2, lorsque bar<T>() est false.

Avec

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
 { }

vous obtenez une erreur de compilation car vous avez une ambiguïté: deux fonctions foo() avec la même signature (un paramètre de modèle par défaut ne change pas la signature).

Mais la solution suivante

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
 { }

fonctionne, car SFINAE modifie la signature des fonctions.

Observation indépendante: il existe également une troisième méthode: activer/désactiver le type de retour (sauf pour les constructeurs de classe/struct, évidemment)

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
 { }

En tant que méthode 2, la méthode 3 est compatible avec la sélection de fonctions alternatives avec la même signature.

35
max66

En plus de réponse de max66 , une autre raison de préférer la méthode 2 est qu'avec la méthode 1, vous pouvez (accidentellement) passer un paramètre de type explicite comme deuxième argument de modèle et vaincre complètement le mécanisme SFINAE. Cela peut se produire comme une faute de frappe, une erreur de copier/coller ou comme une erreur dans un mécanisme de modèle plus grand.

#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;
}

Démo en direct ici

21
alter igel