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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With