34

Given the following function:

void g(int **p1, const int**p2)
{
   if (p1 == p2) { }  
}

clang(back to version 3.0) produces this warning (see it live):

warning: comparison of distinct pointer types ('int **' and 'const int **')
uses non-standard composite pointer type 'const int *const *' 
[-Wcompare-distinct-pointer-types]
  if (p1 == p2) { }
      ~~ ^ ~~

Using -pedantic-errors flags turns it into an error. Neither gcc(back to 4.3.6) nor Visual Studio(2013) produce a warning, according to the standard, is the comparison:

p1 == p2

well formed?

More generally, if two multi-level pointers differ in their cv-qualifications other than at the first level is comparison via the equality operator or relational operators well-formed?

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • very useful to know, upvoted. hope msvc and g++ follow suit in implementing c++14. – Cheers and hth. - Alf Mar 12 '15 at 20:15
  • 1
    @Cheersandhth.-Alf gcc and VS already follow C++14 if they allow this code without a warning or error; clang needs fixing – M.M Mar 12 '15 at 20:23
  • @MattMcNabb: Thanks. Some binary switching in my mind there. – Cheers and hth. - Alf Mar 13 '15 at 01:02
  • @Cheersandhth.-Alf added to my answer a clang commit from five years ago that indicates this was treated as an extension by clang, gcc and EDG and I presume also VS. Note at the meeting it was accepted at it indicated that this changed just codified existing practice. – Shafik Yaghmour Mar 13 '15 at 12:43

1 Answers1

24

Before C++14 this case was ill-formed and the more general case with some exceptions was also ill-formed. This is covered in defect report 1512: Pointer comparison vs qualification conversions , which says:

According to 5.9 [expr.rel] paragraph 2, describing pointer comparisons,

Pointer conversions (4.10 [conv.ptr]) and qualification conversions (4.4 [conv.qual]) are performed on pointer operands (or on a pointer operand and a null pointer constant, or on two null pointer constants, at least one of which is non-integral) to bring them to their composite pointer type.

This would appear to make the following example ill-formed,

bool foo(int** x, const int** y) {
   return x < y;  // valid ?
}

because int** cannot be converted to const int**, according to the rules of 4.4 [conv.qual] paragraph 4. This seems too strict for pointer comparison, and current implementations accept the example.

The defect report points out although this was ill-formed, implementations accepted such comparisons. This clang commit indicates it was treated as an extension and indicates both gcc and EDG also treats this as an extension, presumably this is also the case for Visual Studio.

This was resolved in the standard by N3624: Core Issue 1512: Pointer comparison vs qualification conversions, which says:

This paper presents the modifications to the Working Draft necessary to resolve core issues 583 and 1512. In particular, it makes

[...]

and

void g(int **p1, const int**p2)
{
   if (p1 == p2) { ... }
}

well-formed.

Also note that in the meeting it was accepted it was noted that this just codified existing practice.

Amongst other changes to the standard, this paragraph was added to the end of section 5 [expr], which includes the new term cv-combined type:

The cv-combined type of two types T1 and T2 is a type T3 similar to T1 whose cv-qualification signature (4.4) is:

  • for every j > 0, cv3,j is the union of cv1,j and cv2,j ;
  • if the resulting cv3,j is different from cv1,j or cv2,j , then const is added to every cv3,k for 0 < k < j.

[ Note: Given similar types T1 and T2, this construction ensures that both can be converted to T3. —end note ] The composite pointer type of two operands p1 and p2 having types T1 and T2, respectively, where at least one is a pointer or pointer to member type or std::nullptr_t, is:

  • if both p1 and p2 are null pointer constants, std::nullptr_t;
  • if either p1 or p2 is a null pointer constant, T2 or T1, respectively;
  • if T1 or T2 is “pointer to cv1 void” and the other type is “pointer to cv2 T”, “pointer to cv12 void”, where cv12 is the union of cv1 and cv2 ;
  • if T1 is “pointer to cv1 C1” and T2 is “pointer to cv2 C2”, where C1 is reference-related to C2 or C2 is reference-related to C1 (8.5.3), the cv-combined type of T1 and T2 or the cv-combined type of T2 and T1, respectively;
  • if T1 is “pointer to member of C1 of type cv1 U1” and T2 is “pointer to member of C2 of type cv2 U2” where C1 is reference-related to C2 or C2 is reference-related to C1 (8.5.3), the cv-combined type of T2 and T1 or the cv-combined type of T1 and T2, respectively;
  • if T1 and T2 are similar multi-level mixed pointer and pointer to member types (4.4), the cv-combined type of T1 and T2;
  • otherwise, a program that necessitates the determination of a composite pointer type is ill-formed.

[ Example:

    typedef void *p;
    typedef const int *q;
    typedef int **pi;
    typedef const int **pci;

The composite pointer type of p and q is “pointer to const void”; the composite pointer type of pi and pci is “pointer to const pointer to const int”. —end example ]

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 1
    Under what circumstances could an `int**` and a `const int**` actually be equal, without `reinterpret_cast`? – user253751 Mar 13 '15 at 00:26
  • 1
    @immibis the strict aliasing rule says that the compiler can assume `*x` does not alias `*y` (the same is not true for `**x` and `**y` obviously), so I suspect this test could be optimized to `false` (once it is permitted!) – M.M Mar 13 '15 at 01:55
  • 2
    @MattMcNabb No, it can't. Strict aliasing allows access through "a type similar (as defined in 4.4) to the dynamic type of the object". `const int *` is *similar* to `int *`. – T.C. Mar 14 '15 at 16:28
  • 2
    @immibis The trivial answer is "if both are null pointers". Anyway, converting between `int**` and `const int**` merely requires a `const_cast`, not a `reinterpret_cast`, and I suspect that it is valid to use the resulting pointer in all the usual and expected ways. –  Mar 15 '15 at 17:47