Подтвердить что ты не робот

Сокращение дублирования кода при определении коммутативной операции

У меня есть набор перегрузок коммутативной двоичной функции с именем overlap, которая принимает два разных типа:

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

Моя функция overlap возвращает true тогда и только тогда, когда одна форма перекрывает другую - это один общий пример, используемый при обсуждении multimethods.

Поскольку overlap(a, b) эквивалентен overlap(b, a), мне нужно только реализовать одну "сторону" отношения. Одно повторяющееся решение - написать что-то вроде этого:

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

Но я бы предпочел не писать дополнительные N! / 2 тривиальные версии одной и той же функции, разрешив их сгенерировать вместо этого, используя шаблон.

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

К сожалению, это подвержено бесконечной рекурсии, что неприемлемо: см. http://coliru.stacked-crooked.com/a/20851835593bd557

Как я могу предотвратить такую ​​бесконечную рекурсию? Я правильно подошел к проблеме?

4b9b3361

Ответ 1

Здесь простое исправление:

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

Сам шаблон объявляет целевую функцию, которая будет предпочтительнее, чем рекурсия, потому что она является точным совпадением (не забудьте позаботиться о константе и ссылочной характеристике в вашем реальном случае). Если функция не была реализована, вы получаете ошибку компоновщика:

/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

... который указывает непосредственно на недостающую функцию:)

Ответ 2

Как однажды сказал мудрый человек, нет проблем, которые вы не можете решить с помощью дополнительного слоя косвенности, за исключением слишком большого количества слоев косвенности.

Итак, используйте SFINAE и некоторую косвенность, чтобы сделать это:

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.

Ответ 3

Вы можете переименовать фактический метод в нечто вроде overlap_impl и вызвать его внутри шаблона. Я сломаю рекурсию:

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