Неоднозначные версии ссылок/значений функций

Рассмотрим следующие прототипы функций:

void Remove(SomeContainer& Vec, const std::size_t Index);

SomeContainer Remove(SomeContainer Vec, const std::size_t Index);

Второй реализуется на основе первого. Другими словами, они функционально идентичны во всех отношениях, за исключением того, что один передается по ссылке, а другой — по значению.

Однако GCC говорит, что в подобных случаях это неоднозначно, хотя первая форма — единственная, которая не возвращает значение:

Remove(SomeContainer, 123);

Есть ли обходной путь для этого, или мне нужно придумать разные имена для каждой формы?


person Maxpm    schedule 02.05.2011    source источник
comment
Перегруженные функции не разрешаются по типу возврата, AFAIK.   -  person Oliver Charlesworth    schedule 02.05.2011


Ответы (4)


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

  1. Нет аргументов
  2. Тип аргументов &
  3. Последовательность аргументов

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

Сказав вышеизложенное, передача по значению и передача ссылки создадут неоднозначность для компилятора. Например:

void doSomething(int i)
{
}

void doSomething(int &i)
{
}

int main()
{
    int val = 10;
    doSomething(val);   //Ambiguous
}

Здесь компилятор не может определить, какой версии doSomething() передать val. Он может сделать действительный вызов функции для любой из версий, поэтому он взывает о помощи во время компиляции (поскольку это статическая компоновка) и помечает вызовы как неоднозначные.

В случае, как у вас. Это выбор/предпочтение, чтобы переименовать функции или передать аргумент указателя, который сделает две перегруженные функции (одно и то же имя, но разные типы аргументов). Однако важно учитывать требование и действие, которое функция будет выполнять при выборе предпочтения. Лично я бы не стал выбирать указатель только ради перегрузки. Если мне нужно переустановить или сделать так, чтобы мой аргумент указывал на разные переменные, тогда имело бы смысл выбрать аргумент-указатель.

Простой способ - просто иметь два разных имени функции. Здесь нет накладных расходов, и он так же эффективен, как и любой другой вызов функции.

person Alok Save    schedule 02.05.2011
comment
Я предполагаю, что передача по ссылке/значению также игнорируется? - person Maxpm; 02.05.2011
comment
Передача по ссылке и передача по значению считаются разными типами, и это совершенно нормально. В C++ потенциальная неоднозначность не является ошибкой. Проблема заключается в попытке вызвать его, поскольку компилятор понятия не имеет, должен ли 5 быть int или int&. - person Lstor; 02.05.2011
comment
@Als Что же обычно делают в подобных ситуациях? Являются ли функции просто переименованными, или форма, которая принимает параметр, изменена, чтобы вместо этого принимать указатель? - person Maxpm; 02.05.2011
comment
@Oli: Верно, я думал, есть ли у тебя int x = 5; foo(x);. Определенно плохо сформулировано с моей стороны. - person Lstor; 02.05.2011
comment
@Als: Нет, это неправда. int и int& считаются разными типами. - person Lstor; 02.05.2011

Как уже упоминалось, тип возвращаемого значения не рассматривается для перегрузки. Однако компилятор действительно рассматривает простые значения и ссылается на разные типы, но обычно он не знает, какую версию вызывать. Другими словами, наличие двух перегруженных функций, отличающихся только тем, передается ли параметр по значению или по ссылке, допустимо до тех пор, пока вы не попытаетесь вызвать его: потенциальная двусмысленность не является ошибкой в ​​C++.

Пример:

void f(int) {
    cout << "value\n";
}

void f(int&) {
    cout << "reference\n";
}

int main() {
    int  val = 42;

    f(val); // Error! Ambiguous.
    f(static_cast<int>(val)); // OK: The type is int. Will print "value"
}

Однако я не знаю, как сигнализировать о том, что вы хотите f(int&), поэтому в этом нет большого практического смысла — я просто пытаюсь прояснить, как работает перегрузка C++.

person Lstor    schedule 02.05.2011

Вы можете немного помочь компилятору и пользователям ваших функций, выбрав более отличительные имена:

Container Removed( const Container& c, size_t index );
void Remove( Container& c, size_t index );

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

person xtofl    schedule 02.05.2011

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

person Community    schedule 02.05.2011
comment
Это неправильно, как я указал выше. У вас могут быть как void f(int), так и void f(int&) - проблема возникает при попытке вызвать его. - person Lstor; 02.05.2011
comment
Я имею в виду, что не используется для определения части перегрузки функции. Это есть: определение void f(int) и void f(int&) будет скомпилировано просто отлично, потому что компилятор считает их разными перегрузками. - person Lstor; 02.05.2011
comment
Я также ожидаю, что если вы вызовете f(5);, это должно вызвать версию int, поскольку вы не можете передать ссылку на 5. Но int x = 5; f(x); выдаст ошибку о неоднозначности. - person PieterNuyts; 18.12.2019