Ручная приоритизация перегруженных функций

#c #templates #overloading

#c #шаблоны #перегрузка

Вопрос:

У меня есть несколько перегрузок одной и той же функции. Для некоторого набора аргументов несколько перегрузок подходят одинаково, и я, естественно, получаю ошибку «неоднозначная перегрузка». Я хочу каким-то образом определить приоритеты для этих функций таким образом, чтобы в случае неоднозначности выбирался тот, у которого наименьший приоритет.

Я попытался создать серию вспомогательных классов шаблонов, «тегов», P<0> , P<1> , …, таких, которые P<N> наследуются P<N 1> с некоторой верхней границей. Таким образом, если есть две функции f(P<2>) и f(P<4>) , и я вызываю f(P<0>) , выбирается первая.

На практике это не работает. «Чистый» пример с классическими функциями «int / long» и «long / int» по-прежнему создает двусмысленность. Я попытался добавить в функцию несколько параметров тега, чтобы увеличить «вес» тега, но это не помогает.

Можно ли как-то настроить этот подход?

 constexpr static int MAX = 20;

template<int N, class Enable = void>
class P {};

template<int N>
class P<N, typename std::enable_if<N < MAX, void>::type> : public P<N 1> {};

void f(int, long, P<2>) {}
void f(long, int, P<5>) {}

int main() {
    f(1, 2, P<0>());
}
  

Комментарии:

1. Почему бы вам просто не использовать разные имена для вариантов, чтобы вы могли вызвать тот, который вы хотите явно?

2. Зачем вам это наследование «N-1»? Попробуйте использовать обычную версию, просто template<int N> class P {};

3. @Barmar Потому что на самом деле эти функции являются шаблонными и генерируются с помощью такой логики: «создать функцию для всех векторов», «создать функцию для всех контейнеров STL», «создать функцию для всех сериализуемых типов» и т.д. И для одного типа может быть сгенерировано несколько функций.

4. @ciechowoj Если это так, P<0> невозможно преобразовать в P<2> . Я тебя не понимаю.

5. @IvanSmirnov вы пробовали сделать эти перегрузки шаблонными? пример

Ответ №1:

Прежде всего, вы можете упростить построение типа тега, например:

 template <int N> struct P : P<N 1> { };
template <> struct P<MAX> { };
  

Далее, эскалатор типов тегов работает только в том случае, если они являются единственным ограничителем ваших перегрузок. То есть все остальные аргументы эквивалентны — кроме аргумента тега. Причина, по которой ваши вызовы по-прежнему неоднозначны, заключается в том, что последовательности преобразований, которые вы получаете, являются:

 f(int, long, P<2>); // #1: Exact, Integral Conversion, P<0> -> ... -> P<2>
f(long, int, P<5>); // #2: Integral Conversion, Exact, P<0> -> ... -> P<2> -> ... -> P<5>
  

Одна перегрузка только лучше, чем другая перегрузка, если для каждого аргумента последовательность преобразования по крайней мере так же хороша, как последовательность преобразования другого аргумента перегрузки. Здесь это неверно: #1 лучше в первом аргументе и хуже во втором аргументе, чем #2 . Добавление другого аргумента не меняет этот недостаток.

Типы тегов полезны, если вы используете SFINAE с непересекающимися условиями:

 template <class T, std::enable_if_t<cond_a<T>::value>* = nullptr>
void f(Tamp;amp;, P<0> ) { ... }

template <class T, std::enable_if_t<cond_b<T>::value>* = nullptr>
void f(Tamp;amp;, P<1> ) { ... }

template <class T, std::enable_if_t<cond_c<T>::value>* = nullptr>
void f(Tamp;amp;, P<2> ) { ... }

f(whatever, P<0>{});
  

cond_a , cond_b , и cond_c не обязательно должны быть непересекающимися, и первый аргумент в каждом случае один и тот же. Таким образом, последним ограничителем для тех перегрузок, которые не удаляются SFINAE, является тег.

Комментарии:

1. Хм, я действительно много работал с enable_if в эти дни и не помнил простого решения для тегов 🙂 Спасибо, я почему-то не заметил, что последовательность должна быть лучше для каждого аргумента, хотя я думаю, что все дело в «общей стоимости преобразования». И ваше решение идеально подходит для моей проблемы, кажется, я слишком усложнил вопрос.

2. Спасибо Барри. Я многому научился из этого замечательного ответа. Вот MCVE после того, как я прочитал этот ответ. Мой пример работает для пользовательского класса, но не для int / long.