6 votes

Utilisation de std::enable_if avec des paramètres de type anonyme

J'essaie d'utiliser std::enable_if avec un paramètre de type inutilisé et sans nom, afin de ne pas fausser le fonctionnement du return type. Cependant, le code suivant ne se compile pas.

#include <iostream>

template <typename T, typename = std::enable_if_t<!std::is_integral<T>::value>>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
  foo<float>();
  foo<int>();
}

Le compilateur dit :

7:3: error: redefinition of 'template<class T, class> T foo()'
4:3: note: 'template<class T, class> T foo()' previously declared here
 In function 'int main()':
11:12: error: no matching function for call to 'foo()'
11:12: note: candidate is:
4:3: note: template<class T, class> T foo()
4:3: note: template argument deduction/substitution failed:

Quel est le problème ? Comment dois-je modifier le code pour qu'il compile ? Le manuel "Discovering Modern C++" encourage explicitement l'utilisation de std::enable_if avec des paramètres de type anonyme.

EDIT : Je sais que cela fonctionne si je mets std::enable_if dans le type de retour. Cependant, mon intention est d'obtenir plus de détails sur la raison pour laquelle cela ne fonctionne pas si je l'utilise avec des paramètres de type anonyme. Comme je l'ai dit, mon manuel encourage la variante utilisant des paramètres de type anonyme, et je me demande donc pourquoi mon code ne compile pas.

6voto

skypjack Points 5516

Cependant, mon intention est d'obtenir plus de détails sur la raison pour laquelle cela ne fonctionne pas si je l'utilise avec des paramètres de type anonyme.

Les valeurs par défaut ne participent pas à la résolution des surcharges, ce qui signifie que vous redéfinissez en fait la même fonction.

Simplifions votre exemple :

template<typename = int>
void f() {}

template<typename = void>
void f() {}

int main() {
    f<>();
}

Le code ci-dessus ne compile pas, car il ne peut pas savoir quelle version de f que vous souhaitez invoquer.

Dans votre cas, si j'invoque foo como foo<void, void> J'ai presque le même problème.
Le compilateur ne peut pas deviner mon intention et le fait que le deuxième paramètre ait une valeur par défaut ne signifie pas que vous ne pouvez pas passer un type différent.

De ce fait, le code est mal formé et le compilateur vous signale correctement une erreur.


Par ailleurs, il est possible de faire fonctionner le système sans utiliser l'option std::enable_if_t dans le type de retour.
A titre d'exemple :

#include <type_traits>
#include <iostream>

template <typename T, std::enable_if_t<!std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
    foo<float>();
    foo<int>();
}

Alors que j'essayais de comprendre quelle était l'hypothèse (erronée) du PO et d'expliquer pourquoi cela pouvait être le cas, @T.C. a correctement attiré l'attention sur la véritable raison dans les commentaires de cette réponse.
Il vaut la peine de citer son commentaire pour ajouter plus de détails à la réponse :

Il ne s'agit pas d'une résolution de surcharge, mais d'une correspondance de déclaration. Il n'y a pas deux surcharges pour qu'il y ait une quelconque ambiguïté. Il s'agit de deux erreurs de redéfinition : le modèle de la fonction et l'argument par défaut du modèle.

3voto

KrzaQ Points 1

Vous pouvez mettre enable_if dans le type de retour :

template <typename T>
std::enable_if_t<!std::is_integral<T>::value,T>
foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T>
std::enable_if_t<std::is_integral<T>::value, T>
foo() { std::cout << "integral" << std::endl; return T(); }

A ce propos, enable_if_t est disponible à partir de C++14, vous pourriez donc vouloir dire typename std::enable_if<std::is_integral<T>::value, T>::type au lieu de cela. Une sacrée bouchée.

Mais un peu plus idiomatique (et lisible) serait de distribuer en se basant sur le type :

template <typename T>
T foo_impl(std::false_type) { std::cout << "non-integral" << std::endl; return T(); }

template <typename T>
T foo_impl(std::true_type) { std::cout << "integral" << std::endl; return T(); }

template <typename T>
T foo(){
    return foo_impl<T>(typename std::is_integral<T>::type{});
}

2voto

sasauke Points 301

Il existe plusieurs façons de supprimer les fonctions de la SFINAE. Vous devez généralement vous abstenir d'ajouter un paramètre supplémentaire à la fonction/au modèle et vous contenter de modifier le type de retour.

template <typename T>
auto foo() -> std::enable_if_t<!std::is_integral<T>::value, T>
{ std::cout << "non-integral" << std::endl; return T(); }

template <typename T>
auto foo() -> std::enable_if_t<std::is_integral<T>::value, T>
{ std::cout << "integral" << std::endl; return T(); }

2voto

max66 Points 4276

L'erreur vient du fait que vous utilisez enable_if_t à droite du signe égal.

Vous devez l'utiliser à gauche

#include <iostream>
#include <type_traits>

template <typename T, std::enable_if_t<!std::is_integral<T>::value, int> = 0>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
  foo<float>();
  foo<int>();
}

Mais cela fonctionne avec C++14.

En C++11 (votre question porte sur le C++11), vous n'avez pas de enable_if_t .

Le code devient

#include <iostream>
#include <type_traits>

template <typename T,
          typename std::enable_if<!std::is_integral<T>::value, int>::type = 0>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T,
          typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
  foo<float>();
  foo<int>();
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X