11

The following code compiles well in gcc 7.3.0, but doesn't compiles with clang 6.0.0.

#include <string>

struct X {
    X() : x(10) {}
    int operator[](std::string str) { return x + str[0]; }
    template <typename T> operator T() { return x; } // (1) fails only in clang
    //operator int() { return x; } // (2) fails both in gcc and clang
private:
    int x;
};

int main() {
    X x;
    int y = 20;
    int z = int(x);
    return x["abc"];
}

I used command clang++ 1.cpp -std=c++98 with specifying different standard versions. I tried c++98,11,14,17,2a. In all cases an error is the same. Error message in clang is following:

1.cpp:14:13: error: use of overloaded operator '[]' is ambiguous (with operand types 'X' and 'const char [4]')
    return x["abc"];
           ~^~~~~~
1.cpp:5:9: note: candidate function
    int operator[](std::string str) { return x + str[0]; }
        ^
1.cpp:14:13: note: built-in candidate operator[](long, const char *)
    return x["abc"];
            ^
1.cpp:14:13: note: built-in candidate operator[](long, const volatile char *)
1 error generated.

What compiler correctly follows the standard in this situation? Is it a valid code?

The description of the problem can be found here, but it is about situation (2). I am interested in case (1).

2 Answers2

8

GCC is wrong. The template case shouldn't make any difference.

[over.match.best]/1 says:

Define ICSi(F) as follows:

  • ...

  • let ICSi(F) denote the implicit conversion sequence that converts the i-th argument in the list to the type of the i-th parameter of viable function F. [over.best.ics] defines the implicit conversion sequences and [over.ics.rank] defines what it means for one implicit conversion sequence to be a better conversion sequence or worse conversion sequence than another.

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and ...

The two viable candidates are

int         operator[](X&,             std::string); // F1
const char& operator[](std::ptrdiff_t, const char*); // F2

... and ICS1(F1) (X -> X&) is better than ICS1(F2) (X -> std::ptrdiff_t), no matter whether or not X -> std::ptrdiff_t is through a template conversion function, but ICS2(F1) (const char[4] -> std::string) is worse than ICS2(F2) (const char[4] -> const char*). So neither function is better than the other, resulting in ambiguity.

This has been reported as a GCC bug.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • 1
    To be explicit (to help anyone else who might not get it immediately, like I didn't): There is an ambiguity, so the code should not compile. GCC doesn't recognize the template conversion function as a part of the valid conversion path for calling F2, so it doesn't see any ambiguity and compiles. This misrecognition is described in the linked GCC bug. – Danra Feb 11 '19 at 11:47
  • The lesson of this as that you should not have a cast operator to any integral type for which you've also defined `operator []`. This is one of the C anachronisms that should be abandon IMHO. – Omnifarious Feb 13 '19 at 05:56
6

The issue is that there is one conversion on each path:

  • first from "abc" to std::string and then operator[] call.
  • second from x to std::ptrdiff_t and then the operator[] for an std::ptrdiff_t and a const char*.

So the solution is to make the conversion operator explicit:

int operator[](const std::string& str) { return x + str[0]; }
template <typename T>
explicit operator T() { return x; } // (1) fails only in clang
Language Lawyer
  • 3,378
  • 1
  • 12
  • 29
Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62