Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct behavior of built-in operator candidates of overload resolution in the operator expression context

Currently I'm trying to understand the paragraph [over.match.oper]/7 in the C++ Standard, but encountered the following case where GCC and Clang produce different results:

https://wandbox.org/permlink/WpoMviA4MHId7iD9

#include <iostream>

void print_type(int) { std::cout << "int" << std::endl; }
void print_type(int*) { std::cout << "int*" << std::endl; }

struct X { X(int*) {} };
struct Y { operator double() { return 0.0; } };

int operator+(X, int) { return 0; }   // #1
// T* operator+(T*, std::ptrdiff_t);  // #2: a built-in operator (N4659 16.6/14)

int main() {
  int* p = 0;
  Y y;

  print_type(p + y);  // This line produces different results for different compilers:
                      //   - gcc HEAD 8.0.0   : always "int" (#1 is called)
                      //   - clang HEAD 6.0.0 : always "int*" (#2 is called)
                      //   - my understanding : "int*" until C++11, ill-formed since C++14

  return 0;
}

Description in standards

Here are quotes of the corresponding paragraph from versions of standards:

C++1z (N4659) 16.3.1.2 [over.match.oper] paragraph 7
(essentially the same with C++14 (N4140) 13.3.1.2 [over.match.oper] paragraph 7):

If a built-in candidate is selected by overload resolution, the operands of class type are converted to the types of the corresponding parameters of the selected operation function, except that the second standard conversion sequence of a user-defined conversion sequence (16.3.3.1.2) is not applied. Then the operator is treated as the corresponding built-in operator and interpreted according to Clause 8. [Example:

struct X {
  operator double();
};
struct Y {
  operator int*();
};
int *a = Y() + 100.0; // error: pointer arithmetic requires integral operand
int *b = Y() + X();   // error: pointer arithmetic requires integral operand

- end example]

C++03 13.3.1.2 [over.match.oper] paragraph 7
(essentially the same with C++11 (N3291) 13.3.1.2 [over.match.oper] paragraph 7):

If a built-in candidate is selected by overload resolution, the operands are converted to the types of the corresponding parameters of the selected operation function. Then the operator is treated as the corresponding built-in operator and interpreted according to clause 5.

The change in C++14 was introduced by CWG 1687.

My naive interpretation

I initially thought the top code should be ill-formed in C++14. According to the standards, my naive understanding of the process of the overload resolution of the top code is this (section numbers are from N4659):

First the set of candidate functions is generated. It contains the user-defined operator #1 (16.3.1.2/(3.2)) and a built-in operator #2 (16.3.1.2/(3.3), 16.6/14). Next, to determine the set of viable functions, viability of both operators is tested by constructing implicit conversion sequences (ICS) for each argument/parameter pair; All of the ICSs are successfully constructed as ICS1(#1) = int* → X (16.3.3.1.2, user-defined conversion sequence), ICS2(#2) = Y → double → int (user-defined conversion sequence), ICS1(#2) = int* → int* (16.3.3.1/6, identity conversion, one of the standard conversion sequences) and ICS2(#2) = X → double → std::ptrdiff_t (user-defined conversion sequence), and therefore both operators are viable. Then, the best viable function is selected by comparing ICSs; Since ICS1(#2) is better than ICS1(#1) (16.3.3.2/(2.1)) and ICS2(#2) is not worse than ICS2(#1) (16.3.3.2/3), #2 is a better function than #1 (16.3.3/1). Finally the built-in operator #2 is selected by the overload resolution (16.3.3/2).

When a built-in operator is selected, the rule quoted above (16.3.1.2/7) applies: after applying ICSs to the arguments, treatment of the operator expression is transferred to Clause 8 [expr]. Here the application of ICSs differs in C++11 and C++14. In C++11, ICSs are totally applied, so (int*) y + (std::ptrdiff_t) (double) n is considered, and it's fine. While, in C++14 the second standard conversion sequences in user-defined conversion sequences are not applied, so (int*) y + (double) n is considered. This results in a semantic rule violation (8.7/1), i.e. the expression is ill-formed and implementation is required to issue diagnostic messages.

Clang's interpretation

Clang selects #2 and calls it without any diagnostic messages on 8.7/1 violation. My guess is Clang totally applies ICSs to the arguments before it transfers the call to built-in rules (8.7/1), and this is a bug.

GCC's interpretation

GCC selects #1 without diagnostics. Microsoft C/C++ compiler in Visual Studio 2017 seems to behave the same. Also, this behavior seems reasonable to me (Edit: See [1]).

My guess is GCC considers #2 is not viable and then only the viable function is #1. But I couldn't find any rules like that the built-in operator is not viable when it becomes ill-formed without second standard conversion sequences in user-defined conversion sequences. In fact, when the phrase "except that the second standard conversion sequence of a user-defined conversion sequence" is introduced by CWG 1687, it seems there are no other modifications in the definition of viability.

Question

Question 1: According to the current standard, which is the correct interpretation?

Question 2: If my naive interpretation is correct, is the behavior intended by CWG 1687?


Footnotes

  • [1]: Not to silently break the existing codes written in C++03, this behavior wouldn't be desired. This might be the reason why CWG 1687 decided to just disable the second standard conversion sequence leaving the definition of viability as it is. See comments below.

Update

After this question this issue was reported for the following compilers:

  • gcc gcc 81789
  • clang llvm 34138
  • msc visualstudio 92207
like image 886
akinomyoga Avatar asked Aug 09 '17 23:08

akinomyoga


People also ask

What is overload resolution?

The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.

What are the operators we can overload?

An overloaded operator (except for the function call operator) cannot have default arguments or an ellipsis in the argument list. You must declare the overloaded = , [] , () , and -> operators as nonstatic member functions to ensure that they receive lvalues as their first operands.

Which function is used while performing operator overloading?

Operator function must be either non-static (member function) or friend function.

What is operator overloading Why is it necessary to overload an operator?

The purpose of operator overloading is to provide a special meaning of an operator for a user-defined data type. With the help of operator overloading, you can redefine the majority of the C++ operators. You can also use operator overloading to perform different operations using one operator.


1 Answers

I agree with your interpretation. We have arguments of type int* and Y and we have two candidates:

operator+(X, int);                // #1
operator+(int*, std::ptrdiff_t ); // #2

#1 requires two user-defined conversion sequences, #2 requires an standard conversion sequence (Exact Match, though doesn't matter) and a user-defined conversion sequence. For the first argument, a standard conversion sequence is better than a user-defined conversion sequence, while for the second argument, the two sequences are indistinguishable (none of these conditions apply). Since the first implicit conversion sequence in #2 is better than the first implicit conversion sequence in #1, and the second conversion sequences are equivalent, #2 wins.

And then post-CWG 1687, we don't perform the last conversion from double to ptrdiff_t, so the result should be ill-formed.


To answer this question:

is the behavior intended by CWG 1687?

I suspect it certainly is, given that the example is:

int *b = Y() + X();             // error: pointer arithmetic requires integral operand

which is quite similar to your example - the only difference being that Y() is convertible to int* rather directly being int*. I went ahead and filed gcc 81789 and llvm 34138. Note that clang simply doesn't implement CWG 1687, the examples from that issue and in the standard compile.

like image 72
Barry Avatar answered Oct 06 '22 01:10

Barry