web-dev-qa-db-fra.com

Comment forcer la signature du type de paramètre de modèle?

Je vais utiliser l'exemple suivant pour illustrer ma question:

template<typename T>
T diff(T a, T b)
{
  return a-b;
}

Je m'attends à ce que cette fonction de modèle ne fonctionne que lorsque le type T est signé. La seule solution que je peux trouver est d'utiliser le mot-clé delete pour tous les types non signés:

template<>
unsigned char diff(unsigned char,unsigned char) == delete;
template<>
unsigned char diff(unsigned char,unsigned char) == delete;

Y a-t-il d'autres solutions?

31
feelfree

Vous pouvez utiliser std::is_signed ensemble avec std::enable_if:

template<typename T>
T diff(T a, T b);

template<typename T>
std::enable_if_t<std::is_signed<T>::value, T> diff(T a, T b) {
    return a - b;
}

Ici std::is_signed<T>::value est true si et seulement si T est signé (BTW, c'est aussi true pour les types à virgule flottante, si vous n'en avez pas besoin, envisagez de combiner avec std::is_integral).

std::enable_if_t<Test, Type> est le même que std::enable_if<Test, Type>::type. std::enable_if<Test, Type> est défini comme une structure vide dans le cas où Test est faux et comme une structure avec un seul typedef type égal au paramètre de modèle Type sinon.

Donc, pour les types signés, std::enable_if_t<std::is_signed<T>::value, T> est égal à T, tandis que pour non signé il n'est pas défini et que le compilateur utilise la règle SFINAE, donc, si vous devez spécifier une implémentation pour un type non signé particulier, vous pouvez facilement le faire:

template<>
unsigned diff(unsigned, unsigned)
{
    return 0u;
}

Quelques liens pertinents: enable_if , is_signed .

45
alexeykuzmin0

Que diriez-vous static assert avec std::is_signed ?

template<typename T>
T diff(T a, T b)
{
    static_assert(std::is_signed<T>::value, "signed values only");
    return a-b;
}

Voir en direct là-bas: http://ideone.com/l8nWYQ

35
Louen

J'utiliserais static_assert avec un joli message d'erreur. enable_if n'obtiendra que votre IDE en difficulté et ne pourra pas être compilé avec un message comme

identifiant diff introuvable

ce qui n'aide pas beaucoup.

Alors pourquoi ne pas aimer ça:

#include <type_traits>

template <typename T>
T diff(T a, T b)
{
    static_assert(std::is_signed< T >::value, "T should be signed");
    return a - b;
}

de cette façon, lorsque vous appelez diff avec autre chose qu'un type signé, vous obtiendrez le compilateur pour écrire ce type de message:

erreur: T doit être signé

avec l'emplacement et les valeurs de l'appel à diff et c'est exactement ce que vous recherchez.

11
mister why

Comme autre option, vous pouvez probablement ajouter static_assert avec std :: is_signed trait de type:

template<typename T>
auto diff(T x, T y)
{
    static_assert(std::is_signed<T>::value, "Does not work for unsigned");
    return x - y;
}

Pour que:

auto x = diff(4, 2); // works
auto x = diff(4U, 2U); // does not work
7
Edgar Rokjān

Qu'attend votre programme en conséquence? En l'état, vous retournez un non signé à la suite d'une différence. À mon humble avis, il s'agit d'un bug en attente de se produire.

#include <type_trait>

template<typename T>
auto diff(T&& a, T&& b)
{
    static_assert (std::is_unsigned<T>::value);
    return typename std::make_signed<T>::type(a - b);
}

Une attente plus moderne pour écrire ceci:

inline auto diff(const auto a, const auto b)
{
    static_assert (   std::is_unsigned<decltype(a)>::value 
                   && std::is_unsigned<decltype(b)>::value );
    return typename std::make_signed<decltype(a -b)>::type(a - b);
}

[edit] Je ressens le besoin d'ajouter ce commentaire: utiliser des types intégraux non signés dans les équations mathématiques est toujours délicat. L'exemple ci-dessus serait un complément très utile à n'importe quel package mathématique.Si des situations réelles, vous devez souvent recourir à la conversion pour faire le résultat des différences signed, ou les mathématiques ne fonctionnent pas.

2
Michaël Roy

Il y a donc quelques problèmes que j'ai avec votre fonction.

Tout d'abord, votre fonction nécessite que les 3 types correspondent - les types gauche, droit et résultat. Donc, signed char a; int b; diff(a-b); ne fonctionnera pas sans raison valable.

template<class L, class R>
auto diff( L l, R r )
-> typename std::enable_if<
  std::is_signed<L>::value && std::is_signed<R>::value,
  typename std::decay<decltype( l-r )>::type
>::type
{
  return l-r;
}

la deuxième chose que je voudrais faire est de créer un objet diff; vous ne pouvez pas facilement passer votre fonction diff et les fonctions d'ordre supérieur sont géniales.

struct diff_t {
  template<class L, class R>
  auto operator()(L l, R r)const
  -> decltype( diff(l,r) )
  { return diff(l,r); }
};

Maintenant, nous pouvons passer diff_t{} à un algorithme, car il contient "l'ensemble de surcharge" de diff dans un objet C++ (trivial).

Maintenant, c'est une exagération grave. Un simple static_assert peut également fonctionner.

Le static_assert générera de meilleurs messages d'erreur, mais ne prendra pas en charge d'autres codes utilisant SFINAE pour voir si diff peut être appelé. Cela générera simplement une erreur matérielle.

2