2013-04-06 12 views
11

Próbuję zaimplementować wektor jak i mapę jak operator [] dla klasy. Ale dostaję komunikaty o błędach z moich kompilatorów (g ++ i clang ++). Okazało się, że występują tylko wtedy, gdy klasa ma również operatory konwersji na typy całkowite."Niejednoznaczne przeciążenie dla" operatora [] "" jeśli operator konwersji int istnieje

Teraz mam dwa problemy. Po pierwsze, nie wiem, dlaczego kompilator nie może odróżnić [] (const std :: string &) i kiedy klasa ma operatory konwersji na ints. Drugi ... Potrzebuję konwersji i operatora indeksu. Czy ktoś wie, jak to naprawić?

z góry dzięki i pozdrawiam ze mną

utworów:

#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]; 
} 

nie działa: Błąd

#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]; 
} 

kompilator:

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}' 
+0

Nie ma czegoś takiego jak "operator obsady". Dany operator to ** konwersja **. Obsada to coś, co piszesz w swoim kodzie źródłowym, aby powiedzieć kompilatorowi, aby zrobił konwersję. –

+0

Dziękuję @Pete Becker za wskazanie tego. – r2p2

Odpowiedz

18

Problem polega na tym, że twoja klasa ma operator konwersji do uint32_t, więc kompilator nie wie, czy:

  1. Narysuj std::string z łańcucha dosłownym i powołać swój przeciążenia akceptując std::string;
  2. Przekształć swój obiekt Foo w obiekt uint32_t i użyj go jako indeksu w literale łańcuchowym.

Chociaż opcja 2 może wydawać się mylące, uważają, że poniższe wyrażenie jest legalne w C++:

1["foo"]; 

To dlatego, jak wbudowany operatora indeksem jest zdefiniowana. Na § 8.3.4/6 C++ 11 Standard:

Except where it has been declared for a class (13.5.5), the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)) . Because of the conversion rules that apply to +, if E1 is an array and E2 an integer, then E1[E2] refers to the E2 -th member of E1 . Therefore, despite its asymmetric appearance, subscripting is a commutative operation.

Dlatego powyższe wyrażenie 1["foo"] odpowiada "foo"[1], co ocenia się o. Aby rozwiązać niejednoznaczność, można dokonać konwersji explicit operatora w C++ (11):

struct Foo 
{ 
    explicit operator uint32_t() { /* ... */ } 
// ^^^^^^^^ 
}; 

Albo można pozostawić tego operatora konwersji jak to jest, i zbudować obiekt std::string wyraźnie:

f[std::string("foo")]; 
// ^^^^^^^^^^^^ ^

Alternatywnie, można dodać jeszcze przeciążenia operatora indeksem, który akceptuje const char*, które byłyby lepiej odpowiada niż jeden z powyżej (ponieważ wymaga żadnej obsługi zdefiniowane konwersji)

struct Foo 
{ 
    operator uint32_t() { /* ... */ } 
    Foo& operator[](const std::string &foo) { /* ... */ } 
    Foo& operator[](size_t index) { /* ... */ } 
    Foo& operator[](const char* foo) { /* ... */ } 
    //    ^^^^^^^^^^^ 
}; 

Zauważ również, że twoje funkcje mają nieważny typ zwrotu, ale obecnie brakuje instrukcji return. To wstrzykuje Nieokreślone zachowanie w twoim programie.

+0

Teraz mój mózg boli. Ale myślę, że teraz to rozumiem.Chciałem, aby było to jak najprostsze dla użytkownika. Odrzucanie do int lub std :: string nie pomaga w tym. Sprawia mi trochę smutku. Dziękuję za wyjaśnienia i rozwiązania. – r2p2

+0

@ r2p2: Rozumiem, co masz na myśli, ale "wyraźne" rzutowanie na 'uint_32' nie jest takie złe. W końcu uniemożliwiłoby to takie rzeczy jak 'Foo f; if (f) {/ * ... * /}; 'lub' Foo f; std :: cout << (f + f); 'od kompilacji. –

+0

@ r2p2: Zwróć też uwagę, że w mojej ostatniej aktualizacji sugeruję dalszą alternatywę (użycie dodatkowego przeciążenia, które akceptuje 'const char *'). –

2

Problemem jest to, że f["foo"] może być rozwiązany jak:

  1. Konwersja "foo" do std::string (czy to s) i zrobić f[s] nazywając Foo::operator[](const std::string&).
  2. Konwersja f do liczby całkowitej nazywając Foo::operator int() (może to być i) i zrobić i["foo"] stosując dobrze znany fakt, że wbudowany w [] operatora jest przemienne.

Obie mają jedną niestandardową konwersję typu, stąd dwuznaczność.

Proste rozwiązanie jest dodanie kolejnego przeciążeniem:

Foo& operator[](const char *foo) {} 

Teraz, nazywając f["foo"] wywoła nową przeciążenia, bez konieczności żadnej konwersji Typ niestandardowy, więc niejednoznaczność jest zepsuty.

UWAGA: konwersja z typu (typu type "foo"char[4] do char*) jest uważany za trywialne i nie liczy.

2

Jak zauważono w innych odpowiedzi, problem jest to, że [] dojazdy domyślnie - a[b] jest taka sama jak b[a] dla char const* iz klasa jest zamienny do uint32_t ten jest tak dobry mecz jak char* są przekształcane do std::string .

To, co tu przedstawiam, jest sposobem na "niezwykle atrakcyjne przeciążenie", gdy masz właśnie tego rodzaju problem, w którym przeciążenie nie zostaje wywołane pomimo twojego przekonania, że ​​powinno.

Więc tutaj jest Foo z „niezwykle atrakcyjnej przeciążenia” dla 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)); 
    } 
}; 

gdzie tworzymy wolnostojącą „odnośnika przez ciąg” funkcji, a następnie zapisać szablon, który przechwytuje żadnego typ, który można przekształcić w std::string.

ponieważ „kryje” konwersja zdefiniowane przez użytkownika w korpusie szablonu operator[] podczas sprawdzania dopasowania nie zdefiniowano konwersji użytkownika stanie, więc jest to korzystne dla innych operacji, które wymagają konwersji zdefiniowane przez użytkownika (takie jak uint32_t[char*]). W efekcie jest to "bardziej atrakcyjne" przeciążenie niż jakiekolwiek przeciążenie, które nie jest dokładnie zgodne z argumentami.

Może to prowadzić do problemów, jeśli masz inny przeciążenie, że trwa const Bar& i Bar ma konwersję do std::string powyższy przeciążenie może cię zaskoczyć i uchwycić przekazany w Bar - zarówno rvalues ​​i non-const zmiennych mecz powyższy podpis [] lepiej niż [const Bar&]!

+0

Naprawdę muszę przeczytać tę książkę Metaprogramming szablonu. – r2p2

Powiązane problemy