Краткий общий вопрос:
Есть ли способ обеспечить перегрузку по умолчанию для "резервной" перегрузки шаблона функции?Я читал здесь о некоторых методах переполнения стека, но один требует использования функции с переменным числом (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(...)
.Но потом я столкнулся с другой проблемой.Вариадические функции принимают только тривиальные типы.Но мне нужно использовать эту технику и для пользовательских типов.
Итак, вопрос в том, существует ли какая-либо альтернативная техника для провала воронки с переменной функцией, которая бы обеспечивала наихудшее возможное совпадение?Может быть, даже есть способ сделать это без изменения функциональных аргументов, может быть, путем изменения аргументов шаблона, чтобы я мог использовать технику на реальных операторах, которые имеют фиксированную сигнатуру функциональных аргументов?