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?
[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 typeE
appearing in such a context is said to be contextually implicitly converted to a specified typeT
and is well-formed if and only if e can be implicitly converted to a typeT
that is determined as follows:E
is searched for non-explicit conversion functions whose return type iscv T
or reference tocv T
such thatT
is allowed by the context. There shall be exactly one suchT
.
In your code, there are two such T
s (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.