Это определенная проблема разработки программного обеспечения и языкового дизайна, с которой я постоянно сталкивался, и для которой у меня нет хорошего решения. любой язык. Меня больше всего интересует решение C ++, но было бы также интересно рассмотреть решения на других (надеюсь, в лексически ограниченных) языках.
Вот пример. Допустим, у меня есть фрагмент кода, например, такой:
template<class T, class F>
T foo(T a, T b, T c, T d, F func) { return func() / (a * d - b * c); }
Я утверждаю, что звонящий должен быть в состоянии использовать foo
с модульной арифметикой, а также регулярной арифметики.
Другими словами, для соответствующего определения finite_field
в идеальном мире это должен оцените приведенный выше код в конечном поле, а не в поле действительных чисел:
int main(int argc, char *argv[])
{
finite_field<int> scoped_field(argc /* let's say this is my modulus */);
return foo(1, 2, 3, 4, []{ return +1; }) + foo(4, 3, 2, 1, []{ return -1; });
}
Тем не менее, ясно, что это не работает, потому что ничего внутри foo
(и, в частности, ни один из операторов) не знает об арифметическом контексте, наложенном scoped_field
,
Все решения, о которых я знаю, довольно безобразны:
Прекратите использовать арифметические операторы вообще.
использование add(x, y)
вместо x + y
, div(x, y)
вместо x / y
, так далее.
Тогда, возможно, положить все это внутри Arithmetic
какой-то класс и использовать this
чтобы получить доступ к текущему «арифметическому контексту».
Плюсы: он работает и не требует хранения лишних данных.
Минусы: требует редактирования foo
что, возможно, не должно быть необходимым, и в то же время делает его гораздо менее приятным и намного более трудным для чтения и записи.
Определить обычай ModInt
тип, который оборачивает int
, хранить модуль внутри каждый число, и перегрузите оператор для этого типа, чтобы прочитать модуль из одного из входных аргументов.
Плюсы: это работает, и это не требует изменения тела foo
,
Минусы: неэффективны и подвержены ошибкам — каждый модуль хранится внутри каждый целое число, означающее, что во время выполнения могут возникать конфликтные ошибки, а также очевидная нехватка пространства O (n). Не говоря уже о том, что контекст оценки — это не свойство чисел, а свойство самих операторов.
Сохраните «текущий контекст» внутри локальной переменной потока и перегрузите операторы, чтобы они вели себя по-разному в зависимости от контекста.
Плюсы: это работает (вроде). И это не тратит пространство и не требует модификации foo
,
Минусы: уродливый, менее переносимый, и не реентерабельный, или подверженный ошибкам, в зависимости от того, как он реализован (это загрязнит контексты арифметических операторов вызываемых абонентов)
Так, Я буквально не знаю ни одного решения, которое читабельно, портативно, а также ремонтопригодны.
Насколько я могу судить, я, по сути, должен отказаться от одного из них.
Это общеизвестная или общеизвестная проблема?
Есть ли у него элегантное решение в любой достаточно популярный язык? Если да, то какие и как?
Можно ли это конкретно решить в C ++? Есть какой-то шаблон дизайна или идиома для него?
Тем не менее, небольшая модификация foo
может, вероятно, дать желаемый результат.
template<typename T, typename F, typename F2>
T foo(T a, T b, T c, T d, F func, F2 mod) { return func() / (mod(a) * d - mod(b) * c); }
int main(int argc, char *argv[])
{
auto mod = [](int i) { return ModInt(i, modulus); };
return foo(1, 2, 3, 4, []{ return +1; }, mod) + foo(4, 3, 2, 1, []{ return -1; }, mod);
}
Поскольку каждое использование mod
использует тот же локальный, это невозможно для ModInt
когда-либо иметь разные модули. И с тех пор mod
вызывается только тогда, когда приоритет оператора и таковые требуют этого, и любые локальные объекты уничтожаются и их хранилище используется повторно, тогда мы золотые w.r.t. место для хранения. (Кроме того, если серьезно, несколько целых чисел в стеке? Ничего страшного.)
Но по сути, по модулю арифметики является свойство участвующих операторов. Вот как это определяется и как это работает. Вы можете взломать его, но что бы вы ни делали, это будет хотя бы немного отстой. Вам нужен пользовательский оператор, но вы не можете передать пользовательскую функцию для двух примитивных типов неявным образом, и при этом вы не должны это делать.
Надеюсь, я правильно понимаю ваш вопрос. Короче говоря, я думаю, вы хотите решить во время выполнения, как будет вести себя набор перегруженных арифметических операций. Будет ли что-то вроде этой работы?
struct Arithmetic;
struct MyInt {
int value;
MyInt operator+(const MyInt& other);
static void StandardArithmetic();
static void ModularArithmetic();
static Arithmetic* arithmetic;
};
struct Arithmetic { virtual MyInt Add( MyInt, MyInt ) const = 0; };
struct StandardArithmetic : public Arithmetic {
virtual MyInt Add( MyInt a, MyInt b) const {
MyInt result = {a.value + b.value};
return result;
}
};
struct ModularArithmetic : public Arithmetic { /* ... */ };
И в .cpp
:
Arithmetic * MyInt::Arithmetic = 0;
void MyInt StandardArithmetic() {
delete arithmetic;
arithmetic = new StandardArithmetic;
}
/* ... */
MyInt MyInt::operator+(const MyInt& other) const {
return arithmetic->add(*this, other);
}
Используя это, вы можете легко переключаться между различными типами арифметики, вызывая MyInt::Arithmetic
, Вы также можете сохранить арифметический указатель, например, в статический член абстрактного базового класса, который имеет смысл, если вы хотите реализовать различные арифметические операции.