5

I was trying to post this code as an answer to this question, by making this pointer wrapper (replacing raw pointer). The idea is to delegate const to its pointee, so that the filter function can't modify the values.

#include <iostream>
#include <vector>

template <typename T>
class my_pointer
{
    T *ptr_;

public:
    my_pointer(T *ptr = nullptr) : ptr_(ptr) {}

    operator T* &()             { return ptr_; }
    operator T const*() const   { return ptr_; }
};

std::vector<my_pointer<int>> filter(std::vector<my_pointer<int>> const& vec)
{
    //*vec.front() = 5; // this is supposed to be an error by requirement
    return {};
}

int main()
{
    std::vector<my_pointer<int>> vec = {new int(0)};
    filter(vec);
    delete vec.front(); // ambiguity with g++ and clang++
}

Visual C++ 12 and 14 compile this without an error, but GCC and Clang on Coliru claim that there's an ambiguity. I was expecting them to choose non-const std::vector::front overload and then my_pointer::operator T* &, but no. Why's that?

Community
  • 1
  • 1
LogicStuff
  • 19,397
  • 6
  • 54
  • 74
  • try making the const overload return a reference too – bolov Dec 19 '15 at 15:09
  • 2
    Basically, the standard says that the compiler need to first decide what to convert to, and then run overload resolution. But here it fails at the first step because `int*` and `const int*` are both allowed by the context. – T.C. Dec 19 '15 at 15:10

2 Answers2

8

[expr.delete]/1:

The operand shall be of pointer to object type or of class type. If of class type, the operand is contextually implicitly converted (Clause [conv]) to a pointer to object type.

[conv]/5, emphasis mine:

Certain language constructs require conversion to a value having one of a specified set of types appropriate to the construct. An expression e of class type E appearing in such a context is said to be contextually implicitly converted to a specified type T and is well-formed if and only if e can be implicitly converted to a type T that is determined as follows: E is searched for non-explicit conversion functions whose return type is cv T or reference to cv T such that T is allowed by the context. There shall be exactly one such T.

In your code, there are two such Ts (int * and const int *). It is therefore ill-formed, before you even get to overload resolution.


Note that there's a change in this area between C++11 and C++14. C++11 [expr.delete]/1-2 says

The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (12.3.2) to a pointer to object type. [...]

If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, [...]

Which would, if read literally, permit your code and always call operator const int*() const, because int* & is a reference type, not a pointer to object type. In practice, implementations consider conversion functions to "reference to pointer to object" like operator int*&() as well, and then reject the code because it has more than one qualifying non-explicit conversion function.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • I've just tried to add `get` function and [it worked](http://coliru.stacked-crooked.com/a/67adffa698e65623), so this only happens with implicit conversion operators? – LogicStuff Dec 19 '15 at 15:26
  • 1
    @LogicStuff Correct. Calling a member function just does ordinary overload resolution, and if it returns a pointer `delete` is happy. – T.C. Dec 19 '15 at 15:28
1

The delete expression takes a cast expression as argument, which can be const or not.

vec.front() is not const, but it must first be converted to a pointer for delete. So both candidates const int* and int* are possible candidates; the compiler cannot choose which one you want.

The eaiest to do is to use a cast to resolve the choice. For example:

delete (int*)vec.front();

Remark: it works when you use a get() function instead of a conversion, because the rules are different. The choice of the overloaded function is based on the type of the parameters and the object and not on the return type. Here the non const is the best viable function as vec.front()is not const.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 1
    I don't think this is a full answer because the compiler always chooses the non-const versions of functions for non-const objects for normal function calls. There is no ambiguity in that case, so why should there be in this case? – Kevin Dec 19 '15 at 15:14