"неоднозначная перегрузка для оператора []", если оператор преобразования int int существует
Я пытаюсь реализовать оператор типа vector и карту типа TG40 для класса. Но я получаю сообщения об ошибках от моих компиляторов (g++ и клан g++). Выяснилось, что они возникают только в том случае, если в классе есть также операторы преобразования в целочисленные типы.
Теперь у меня две проблемы. Во-первых, я не знаю, почему компилятор не может различить [](const std::string&)
и [](size_t)
, когда в классе есть операторы преобразования в целые.
Второе... Мне нужно преобразование и оператор индекса. Как это исправить?
работы:
#include <stdint.h>
#include <string>
struct Foo
{
Foo& operator[](const std::string &foo) {}
Foo& operator[](size_t index) {}
};
int main()
{
Foo f;
f["foo"];
f[2];
}
не работает:
#include <stdint.h>
#include <string>
struct Foo
{
operator uint32_t() {}
Foo& operator[](const std::string &foo) {}
Foo& operator[](size_t index) {}
};
int main()
{
Foo f;
f["foo"];
f[2];
}
ошибка компилятора:
main.cpp: In function 'int main()':
main.cpp:14:9: error: ambiguous overload for 'operator[]' in 'f["foo"]'
main.cpp:14:9: note: candidates are:
main.cpp:14:9: note: operator[](long int, const char*) <built-in>
main.cpp:7:7: note: Foo& Foo::operator[](const string&)
main.cpp:8:7: note: Foo& Foo::operator[](size_t) <near match>
main.cpp:8:7: note: no known conversion for argument 1 from 'const char [4]' to 'size_t {aka long unsigned int}'
Ответы
Ответ 1
Проблема в том, что ваш класс имеет оператор преобразования до uint32_t
, поэтому компилятор не знает, следует ли:
- Построить a
std::string
из строкового литерала и вызвать вашу перегрузку, принимая std::string
;
- Преобразуйте объект
Foo
в uint32_t
и используйте его как индекс в строковый литерал.
В то время как вариант 2 может казаться запутанным, считайте, что следующее выражение является законным в С++:
1["foo"];
Это связано с тем, как определяется встроенный оператор нижнего индекса. В пункте 8.3.4/6 стандарта С++ 11:
За исключением того, что он был объявлен для класса (13.5.5), индексный оператор [] интерпретируется в таком способ, который E1[E2]
идентичен *((E1)+(E2))
. Из-за правил преобразования, которые применяются к +, если E1
является array и E2
целое число, то E1[E2]
относится к E2
-му члену E1
. Поэтому, несмотря на свою асимметричную внешний вид, подписка является коммутативной операцией.
Следовательно, приведенное выше выражение 1["foo"]
эквивалентно "foo"[1]
, которое оценивается как o
. Чтобы устранить двусмысленность, вы можете сделать оператор преобразования explicit
(в С++ 11):
struct Foo
{
explicit operator uint32_t() { /* ... */ }
// ^^^^^^^^
};
Или вы можете оставить этот оператор преобразования как есть, и явно построить объект std::string
:
f[std::string("foo")];
// ^^^^^^^^^^^^ ^
В качестве альтернативы вы можете добавить дополнительную перегрузку оператора индекса, который принимает const char*
, что было бы лучше, чем любое из вышеперечисленных (поскольку оно не требует никакого пользовательского преобразования):
struct Foo
{
operator uint32_t() { /* ... */ }
Foo& operator[](const std::string &foo) { /* ... */ }
Foo& operator[](size_t index) { /* ... */ }
Foo& operator[](const char* foo) { /* ... */ }
// ^^^^^^^^^^^
};
Также обратите внимание, что ваши функции имеют не-void возвращаемый тип, но в настоящее время пропустите инструкцию return
. Это вставляет Undefined Поведение в вашу программу.
Ответ 2
Проблема заключается в том, что f["foo"]
можно решить как:
- Преобразуйте
"foo"
в std::string
(будь то s
) и выполните f[s]
вызов Foo::operator[](const std::string&)
.
- Преобразовать
f
в целочисленный вызов Foo::operator int()
(be it i
) и сделать i["foo"]
с помощью известного факта, что встроенный оператор []
является коммутативным.
Оба имеют одно настраиваемое преобразование типа, следовательно, двусмысленность.
Простое решение - добавить еще одну перегрузку:
Foo& operator[](const char *foo) {}
Теперь вызов f["foo"]
вызовет новую перегрузку, не требуя какого-либо преобразования настраиваемого типа, поэтому неясность будет нарушена.
ПРИМЕЧАНИЕ. Преобразование из типа char[4]
(тип типа "foo"
) в char*
считается тривиальным и не учитывается.
Ответ 3
Как указано в других ответах, ваша проблема заключается в том, что []
коммутирует по умолчанию - a[b]
совпадает с b[a]
для char const*
, а ваш класс может быть конвертирован в uint32_t
, это так же хорошо совпадение, когда char*
преобразуется в std::string
.
То, что я предоставляю здесь, - это способ сделать "чрезвычайно привлекательную перегрузку", когда у вас есть именно такая проблема, когда перегрузка не вызывается, несмотря на то, что вы полагаете, что она должна.
Итак, вот Foo
с "чрезвычайно привлекательной перегрузкой" для std::string
:
struct Foo
{
operator uint32_t() {return 1;}
Foo& lookup_by_string(const std::string &foo) { return *this; }
Foo& operator[](size_t index) {return *this;}
template<
typename String,
typename=typename std::enable_if<
std::is_convertible< String, std::string >::value
>::type
> Foo& operator[]( String&& str ) {
return lookup_by_string( std::forward<String>(str) );
}
};
где мы создаем функцию "поиск по строкам", а затем создаем шаблон, который захватывает любой тип, который может быть преобразован в std::string
.
Поскольку он "скрывает" пользовательское преобразование в теле шаблона operator[]
, при проверке соответствия не происходит никакого преобразования, определенного пользователем, поэтому это предпочтительнее других операций, для которых требуются определенные пользователем преобразования (например, uint32_t[char*]
). По сути, это "более привлекательная" перегрузка, чем любая перегрузка, которая точно не соответствует аргументам.
Это может привести к проблемам, если у вас есть другая перегрузка, которая принимает const Bar&
, а Bar
имеет преобразование в std::string
, вышеупомянутая перегрузка может удивить вас и захватить пройденный в Bar
- оба rvalues и неконстантные переменные соответствуют вышеуказанной []
сигнатуре лучше, чем [const Bar&]
!