"неоднозначная перегрузка для оператора []", если оператор преобразования 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&]!