Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

g++ and clang++ - delete pointer acquired by overloaded conversion operator ambiguity

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?

like image 876
LogicStuff Avatar asked Dec 19 '15 14:12

LogicStuff


2 Answers

[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.

like image 89
T.C. Avatar answered Nov 14 '22 21:11

T.C.


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.

like image 24
Christophe Avatar answered Nov 14 '22 22:11

Christophe