Можете ли вы (и должны ли вы) устранить неоднозначность вызова функции, взяв T и const ссылку на T? - PullRequest
6 голосов
/ 05 августа 2020

Если у нас есть:

void foo(int) {}
void foo(const int&) {}

, мы не можем вызвать foo вот так:

foo(3);

, потому что вызов неоднозначен:

error: call of overloaded 'foo(int)' is ambiguous
40 |     foo(3);
   |          ^
note: candidate: 'void foo(int)'
36 | void foo(int) {}
   |      ^~~
note: candidate: 'void foo(const int&)'
37 | void foo(const int&) {}
   |      ^~~

Что мы можем сделать, так это явно указать правильную перегрузку, например, с помощью указателя на функцию:

auto (*ptr)(const int&) -> void = foo;
ptr(3); // calls the "const int&" overload, obviously

Однако такой вид побеждает цель удобных перегрузок. Вопрос в том, можно ли как-нибудь устранить неоднозначность вызова более ... элегантным? способом? Существуют ли когда-либо случаи, когда было бы желательно предоставить обе перегрузки для T и const T&?

Ответы [ 3 ]

3 голосов
/ 05 августа 2020

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

#include <iostream>

void foo(int n)
{
    std::cout << "By Value  " << n;
}

template<int N = 0>
void foo(const int& n)
{
    std::cout << "By Reference " << n;
}

int main() {
    foo(1);
    foo<>(1);
}

Конечно, вам нужно <> для вызова шаблона, но это могло иметь какое-то применение. Якобы более элегантный, чем указатель на функцию? Но, увы, это не намного лучше, чем переименование, скажем, foo<> в bar.

1 голос
/ 06 августа 2020

мы не можем вызвать foo следующим образом: foo(3);, потому что вызов неоднозначен

В общем, именно поэтому разработчики избегают предоставления перегрузок , которые похожи. Это затрудняет использование перегрузок. Как правило, у разработчиков есть перегрузки const T& и T&&, поскольку они никогда не бывают двусмысленными.

Однако такой вид побеждает цель удобных перегрузок. * Цель удобных перегрузок - позволить вызывающему коду легко вызывать нужную функцию. Вызов ровно одного выполняет цель удобных перегрузок. Раздражение, связанное с приведением к указателю функции, является побочным эффектом наличия перегрузок, слишком близких к собиранию, и это ненормально. как функтоид, и пусть этот вызывающий абонент выбирает, какую перегрузку он хочет.

struct foo_functoid {
    void operator()(int v) {foo(v);}
    void operator()(const int& v) {foo(v);}
};
//or
struct foo_functoid {
    template<class T>
    void operator()(T&& v) {foo(std::forward<T>(v));}
};

Но, к сожалению, для этих функтоидов нет ярлыка, они должны быть написаны вручную (с возможно незначительная помощь макросов)

1 голос
/ 05 августа 2020

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

Насколько мне известно, единственный способ заставить разрешение перегрузки выбрать перегрузку int вместо перегрузки const int& - это привести аргумент к volatile int glvalue, и нет способа принудительно разрешить перегрузку выбрать перегрузку const int& поверх int перегрузка.

В любом случае, я не могу придумать ни одной причины, по которой можно было бы написать этот конкретный набор перегрузок.

...