Перегрузка функций

MQL5 допускает определение в исходном коде функций с одним и тем же именем, но разным количеством или типом параметров. Такой подход называется перегрузкой функций. Он обычно применяется, когда одно и то же по смыслу действие может быть инициировано отличающимися входными данными. Благодаря различиям в сигнатурах компилятор способен автоматически определить, какую именно функцию следует вызвать, руководствуясь передаваемыми аргументами. Но существует несколько нюансов.

Функции не могут различаться только типом возвращаемого значения. Такая ситуация не запускает механизм перегрузки и будет генерировать ошибку "функция уже определена с другим типом" ("function already defined and has different type").

Кроме того, если одноименные функции отличаются количеством параметров, но "лишние" параметры объявлены необязательными, то компилятор не сможет "решить", какую из них вызвать. В результате мы получим ошибку "неоднозначный вызов перегруженной функции с одинаковыми параметрами" ("ambiguous call to overloaded function with the same parameters").

При вызове перегруженной функции компилятор производит сопоставление аргументов и параметров в имеющихся перегрузках. Если не удалось найти точного совпадения, компилятор "пробует" добавить/удалить модификатор const, выполнить расширение числового типа и арифметическое преобразование. В случае указателей на объекты используются правила наследования классов.

При различном количестве параметров или неродственных типах параметров в одной и той же позиции (например, число и строка), выбор обычно очевиден. Однако если типы параметров подлежат неявной конвертации из одного в другой, может появиться неоднозначность.

Например, представим две функции суммирования:

double sum(double v1double v2)
{
   return v1 + v2;
}
 
int sum(int v1int v2)
{
   return v1 + v2;
}

Тогда следующий вызов приведет к ошибке:

sum(13.14); // ambiguous call to overloaded function

Здесь компилятору в равной степени неудобна каждая из перегрузок: для функции double sum(double v1, double v2) необходимо неявно преобразовать первый аргумент в double, а для функции int sum(int v1, int v2) — второй аргумент в int.

Термин перегрузка следует трактовать в том смысле, что многократно использованное имя "загружено" "обязанностями" в несколько раз сильнее, чем обычное имя, использованное только для одной функции.

Попробуем перегрузить функцию для транспонирования матрицы. У нас уже был пример для массива размером 2x2 (см. Параметры-значения и параметры-ссылки). Реализуем такую же операцию для массива 3x3. Размер многомерного параметра-массива в старших измерениях (ненулевом) изменяет тип, то есть double [][2] отличается от double [][3]. Таким образом, мы перегрузим старую версию функции:

void Transpose(double &m[][2]);

добавив новую (FuncOverload.mq5):

void Transpose(double &m[][3]);

В реализации новой версии нам удобно применить вспомогательную функцию Swap для обмена двух элементов матрицы по заданным индексам.

void Transpose(double &m[][3])
{
   Swap(m01);
   Swap(m02);
   Swap(m12);
}
 
void Swap(double &m[][3], const int iconst int j)
{
   static double temp;
   
   temp = m[i][j];
   m[i][j] = m[j][i];
   m[j][i] = temp;
}

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

double a[2][2] = {{12}, {34}};
Transpose(a);
...
double b[3][3] = {{123}, {456}, {789}};
Transpose(b);

Важно отметить, что модификатор const у параметра хоть и меняет прототип функции, но не всегда является достаточным различием для перегрузки. Две одноименных функции, которые отличаются только присутствием и отсутствием const у какого-либо параметра, могут считаться одинаковыми. В результате возникнет ошибка "function already defined and has body". Данное поведение обусловлено тем, что для параметров-значений модификатор const отбрасывается при назначении аргумента (поскольку параметр-значение по определению не сможет изменить аргумент в вызывающем коде), и это не позволяет выбрать на его основе одну из нескольких перекрытых функций.

Чтобы продемонстрировать это, достаточно в скрипте добавить функцию:

void Swap(double &m[][3], int iint j);

Она является неудачной перегрузкой для уже имеющейся:

void Swap(double &m[][3], const int iconst int j);

Различие двух функций заключается только в модификаторах const у параметров i и j. Поэтому они обе подходят для вызова с аргументами типа int и передачей по значению.

Когда параметры передаются по ссылке, перегрузка с различием только в const/не-const атрибутах совершается успешно, потому что для ссылок модификатор const важен (меняет тип и исключает возможность неявного преобразования). Это демонстрируется в скрипте парой функций:

void SwapByReference(double &m[][3], int &iint &j)
{
   Print(__FUNCSIG__);
}
 
void SwapByReference(double &m[][3], const int &iconst int &j)
{
   Print(__FUNCSIG__);
}
 
void OnStart()
{
   // ...
   {
      int i = 0j = 1;
      SwapByReference(bij);
   }
   {
      const int i = 0j = 1;
      SwapByReference(bij);
   }
}

Они оставлены в виде практически пустых заглушек, в которых печатается сигнатура каждой функции с помощью вызова Print(__FUNCSIG__). Это дает возможность убедиться, что соответствующая версия функции вызывается в зависимости от атрибута const аргументов.