Резервная перегрузка по умолчанию для шаблона функции - PullRequest
0 голосов
/ 29 ноября 2018

Краткий общий вопрос:

Есть ли способ обеспечить перегрузку по умолчанию для "резервной" перегрузки шаблона функции?Я читал здесь о некоторых методах переполнения стека, но один требует использования функции с переменным числом (f(...)), которая имеет некоторые серьезные недостатки в моем случае, а другие требуют перечисления всех перегруженных комбинаций типов, которые слишком многословны и не автоматизированы.достаточно для моих нужд.Этот вопрос задавался, но ему уже несколько лет, поэтому мне было интересно, есть ли какие-то новые решения для этого, использующие функции новейших стандартов и, возможно, даже некоторые решения, которые будут возможны в C ++ 20 с использованием концепций.

Длинный подробный вопрос:

Я пытаюсь реализовать что-то вроде динамической типизации в C ++.Дело в том, что у меня есть несколько «абстрактных» типов, таких как Bool, Integer, Decimal, String и т. Д., Которые представлены некоторыми встроенными типами, т.е. Integer хранится как long long, но любой целочисленный тип, кроме bool, преобразуется в него.

Теперь я начал реализовывать операторы.Я мог бы реализовать каждую возможную перегрузку (например, Bool + Integer, Integer + Integer, ...) вручную, но я хотел бы иметь только одну реализацию для некоторой "категории", то есть Decimal + any_lower_type_than<Decimal> (commutative), где any_lower_type_than<Integer> относится к определенной иерархииабстрактных типов.У меня есть все необходимые метафункции, чтобы различать категории и реализованную иерархию.Затем я использую SFINAE с этими метафункциями для предоставления определений.Например:

// decimal + lower, commutative:
template <typename first_t, typename second_t,
    std::enable_if_t<commutative_with_lower<first_t, second_t, Decimal>::value>* = nullptr>
Decimal operator+ (const first_t& first, const second_t& second) {
    return first + second;
}

// string * lower than integer, commutative:
template <typename first_t, typename second_t,
    std::enable_if_t<commutative_with_lower_than<first_t, second_t, String, Integer>::value>* = nullptr>
String operator* (const first_t& first, const second_t& second) {
    String string_arg = is_string<first_t>::value ? first : second;
    auto other_arg = is_string<first_t>::value ? second : first;
    String result = "";
    for (decltype(other_arg) i = 0; i < other_arg; ++i)
        result += string_arg;
    return result;
}

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

Я читал о том, что переменная функция «воронка» хуже, чем что-либо другое.Таким образом, естественным подходом было бы обеспечить запас в виде operator+ (...), что, конечно, невозможно.Поэтому я изменил все операторы на обычные функции, например, operator_add, а затем реализовал сам operator+ на более высоком уровне, вызвав соответствующую перегрузку operator_add, где перегрузка по умолчанию реализована как переменная функция operator_add(...).Но потом я столкнулся с другой проблемой.Вариадические функции принимают только тривиальные типы.Но мне нужно использовать эту технику и для пользовательских типов.

Итак, вопрос в том, существует ли какая-либо альтернативная техника для провала воронки с переменной функцией, которая бы обеспечивала наихудшее возможное совпадение?Может быть, даже есть способ сделать это без изменения функциональных аргументов, может быть, путем изменения аргументов шаблона, чтобы я мог использовать технику на реальных операторах, которые имеют фиксированную сигнатуру функциональных аргументов?

Ответы [ 3 ]

0 голосов
/ 29 ноября 2018

Таким образом, вопрос заключается в следующем: существует ли какая-либо альтернативная техника для провала скважины с вариадической функцией, которая обеспечивала бы наихудшее возможное совпадение?

Вы можете использовать иерархию для упорядочения перегрузок:

template <std::size_t N> struct overloadPriority : overloadPriority<N -1> {};
template <> struct overloadPriority<0>{};

затем

template <typename T>
std::enable_if_t<MyTrait5<T>::value> foo_impl(T&&, overloadPriority<5>) {/*..*/}

template <typename T>
std::enable_if_t<MyTrait4<T>::value> foo_impl(T&&, overloadPriority<4>) {/*..*/}

// ...

template <typename T>
void foo_impl(T&&, overloadPriority<0>) {/*..*/} // Fallback


template <typename T>
auto foo(T&& t)
{
    return foo_impl(std::forward<T>(t), overloadPriority<42>{});
    // number greater or equal to max overload_priority used by that overload.
}
0 голосов
/ 29 ноября 2018

Вы можете просто определить свой запасной вариант, чтобы иметь конечный пакет параметров:

template <typename A, typename B, typename... T>
whatever_t operator_add(A&& a, B&& b, T...)
{
    …
}

Перегрузка всегда будет отдавать предпочтение шаблонам, а не шаблонам, и предпочитать более специализированный шаблон менее специализированному шаблону.Основано на [temp.deduct.partial] / 11 , если два шаблона функций в остальном одинаково хорошо подходят, а тот, у которого заканчивается пакет параметров, теряет.

Единственным недостатком этого подхода являетсячто будут потенциальные проблемы, если вы когда-нибудь планируете добавить перегрузки operator_add(), которые принимают более двух аргументов.Кроме того, если кто-то случайно передаст больше двух аргументов, будет вызван запасной вариант и просто проглотит их.К сожалению, из-за проблемы, упомянутой в комментариях Jarod42, мы не можем просто использовать std::enable_if для устранения перегрузки в случае, если на самом деле есть аргументы для пакета параметров (как я изначально предлагал).Одна вещь, которую вы могли бы сделать, чтобы хотя бы защититься от случайного вызова, это просто добавить еще одну перегрузку, которая будет вызываться в этом случае, и определить ее как удаленную:

template <typename A, typename B, typename C, typename... T>
void operator_add(A&&, B&&, C&&, T...) = delete;
0 голосов
/ 29 ноября 2018

Добавьте еще один параметр к вашим operator_add с, включая запасной вариант.

Присвойте дополнительному параметру запасного параметра другой тип (скажем, long), чем остальные (скажем, int).

Заставьте operator+ вызвать его с аргументом int, чтобы у запасного варианта было худшее преобразование.

...