web-dev-qa-db-fra.com

Réduire la duplication de code tout en définissant une opération commutative

J'ai un ensemble de surcharges d'une fonction binaire commutative nommée overlap, qui accepte deux types distincts:

class A a; class B b;
bool overlap(A, B);
bool overlap(B, A);

Ma fonction overlap retourne vrai si et seulement si une forme chevauche l'autre - c'est un exemple courant utilisé lors de la discussion multiméthodes .

Parce que overlap(a, b) est équivalent à overlap(b, a), je n'ai besoin que d'implémenter un "côté" de la relation. Une solution répétitive consiste à écrire quelque chose comme ceci:

bool overlap(A a, B b) { /* check for overlap */ }
bool overlap(B b, A a) { return overlap(a, b);   }

Mais je préférerais ne pas écrire de versions triviales supplémentaires N! / 2 De la même fonction en permettant leur génération à la place, en utilisant un modèle.

template <typename T, typename U> 
bool overlap(T&& t, U&& u) 
{ return overlap(std::forward<U>(u), std::forward<T>(t)); }

Malheureusement, cela a tendance à récurer à l'infini, ce qui n'est pas acceptable: voir http://coliru.stacked-crooked.com/a/20851835593bd557

Comment puis-je empêcher une telle récursion infinie? Suis-je approcher le problème correctement?

50
mbozzi

Voici une solution simple:

template <typename T, typename U> 
void overlap(T t, U u)
{
    void overlap(U, T);
    overlap(u, t);
}

Le modèle lui-même déclare la fonction cible, qui sera préférée à la récursivité car c'est une correspondance exacte (assurez-vous de prendre soin de la constance et de la référence dans votre cas réel). Si la fonction n'a pas été implémentée, vous obtenez une erreur de l'éditeur de liens:

/tmp/cc7zinK8.o: In function `void overlap<C, D>(C, D)':
main.cpp:(.text._Z7overlapI1C1DEvT_T0_[_Z7overlapI1C1DEvT_T0_]+0x20):
    undefined reference to `overlap(D, C)'
collect2: error: ld returned 1 exit status

... qui pointe directement sur la fonction manquante :)

64
Quentin

Comme l'a dit un sage, il n'y a pas de problème que vous ne pouvez pas résoudre avec une couche d'indirection supplémentaire, sauf trop de couches d'indirection.

Donc, utilisez SFINAE et une indirection pour le faire:

template<class A, class B>
auto overlap(A&& a, B&& b)
-> decltype(overlap_impl('\0', std::forward<A>(a), std::forward<B>(b)))
{ return overlap_impl('\0', std::forward<A>(a), std::forward<B>(b)); }

template<class A, class B>
auto overlap_impl(int, A&& a, B&& b)
-> decltype(do_overlap(std::forward<A>(a), std::forward<B>(b)))
{ return do_overlap(std::forward<A>(a), std::forward<B>(b)); }

template<class A, class B>
auto overlap_impl(long, B&& b, A&& a)
-> decltype(do_overlap(std::forward<A>(a), std::forward<B>(b)))
{ return do_overlap(std::forward<A>(a), std::forward<B>(b)); }

// You can provide more choices if you want, for example to use member-functions.

// Implement `do_overlap(A, B)`, maybe with references, in at least one direction.
13
Deduplicator

Vous pouvez renommer la méthode actuelle en quelque chose comme overlap_impl et appelez celui-ci dans le modèle. Je vais casser la récursivité:

bool overlap_impl(A a, B b) { /* check for overlap */ }

template <typename T, typename U> 
bool overlap(T&& t, U&& u) 
{ return overlap_impl(std::forward<U>(u), std::forward<T>(t)); }

template<> bool overlap(A&& t, B&& u)
{ return overlap_impl(std::forward<A>(t), std::forward<B>(u)); }
1
K. Kirsz