5

I have following code:

template <typename T>
struct wrapper {
    T t;
    operator T() { return t; }
    T get() { return t; }
};

int main() {
    int a[10];
    int* x = a;
    wrapper<long unsigned int> y{2};
    std::cout << (x + y); // warning
}

When I compile it on gcc (tested on 7.3.0 and 8.2.0) with -Wsign-conversion I get "warning: conversion to 'long int' from 'long unsigned int' may change the sign of the result". If y has type long unsigned int, there is no warning. Moreover when I explicitly call y.get() there is also no warning:

std::cout << (x + y.get()); // this is ok

Why this is the case? Are there some special rules for pointer arithmetic which cannot be used when using user-defined conversion?

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Igor
  • 1,307
  • 1
  • 10
  • 19
  • 1
    Note that, IIRC, pointer arithmetic is defined only for array elements. – Daniel Langr Dec 05 '18 at 09:38
  • 2
    For `x + y`, overload resolution is performed and the built-in candidate `T* operator+(T*, std::ptrdiff_t);` is selected. However, [the conversion from `long unsigned int` to `std::ptrdiff_t` does not actually happen](http://eel.is/c++draft/over.match.oper#9), and it seems GCC neglects that when producing the warning. – cpplearner Dec 05 '18 at 09:39
  • @DanielLangr right, I made an edit to the question, so that `a` is now array type. – Igor Dec 05 '18 at 09:48

1 Answers1

2

Seems like a compiler issue/bug

(thanks @liliscent for correcting what I said here earlier)

First, let's make a single minimal reproducible example for all of the statement you were mentioning:

#include <iostream>

template <typename T>
struct wrapper {
    T t;
    operator T() const { return t; }
    T get() const { return t; }
};

int main() {
    int a[10];
    int* x { a } ;
    wrapper<long int> y1{2};
    wrapper<unsigned int> y2{2};
    wrapper<long unsigned int> y3{2};

    std::cout << (x + y1) << '\n';
    std::cout << (x + y2) << '\n';
    std::cout << (x + y3) << '\n'; // this triggers a warning
    std::cout << (x + y3.get()) << '\n';
}

and using GCC 8.2.0, we get:

<source>: In function 'int main()':
<source>:20:23: warning: conversion to 'long int' from 'long unsigned int' may change the sign of the result [-Wsign-conversion]
     std::cout << (x + y3) << '\n';
                       ^~
Compiler returned: 0

at the link, you will see how:

  • GCC emits this error with all (recent) versions.
  • Clang emits this error with version 6.0.
  • Clang does not emits this error with version 7.0.

So it must be some corner case w.r.t. standards compliance.

... but don't go into "Here Be Dragons" territory.

Now, I'm sure there's a complex technical explanation regarding why you only get an error on the 3rd of these streaming statements. But I claim it does not matter from a practical use perspective. If you stick to only adding proper integers to pointers - like you do with the .get() statement - you wouldn't get that warning.

You see, you are trying to add up a user-defined type to a pointer - which doesn't make much sense generally. It's true that your structure can be converted into an integer, but relying on this conversion to be made implicitly opens you up to things like the choice to convert the other operand, or other conversion paths you have not considered. What's more, these are cases in which you might 'hit' some esoteric clause in the standard regarding when implicit conversions are legit, which may or may not be implemented with perfect correctness by the compiler (see @cppcleaner's comment).

So just use x + y3.get() in your code and you'll not have to worry about these esoteric corner cases.

I have made a similar argument in this answer regarding the use of index 0 of an empty string (yes, that's a thing).

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 2
    I am afraid this doesn't answer the question as posted. I would totally understand this being a 2nd part of an answer, where the 1st part would explain the reason. So that the answer would then read "here's why; but please do something sane instead, like this." But without the 1st part, it's not an answer. Understanding why something happens can be more valuable than "a solution which works because it does." – Angew is no longer proud of SO Dec 05 '18 at 09:53
  • @Angew: In some cases, understanding why something happens is valuable. In this case, I am arguing that it is valuable to _not_ understand why this happens (unless you're exploring the behavior of GCC rather than writing C++). But I'll try to edit slightly to emphasize that. – einpoklum Dec 05 '18 at 09:56
  • @Angew... but added a first part to the answer anyway. – einpoklum Dec 05 '18 at 10:02
  • *It looks like clang doesn't warn you* --> Wrong. You should pass `-Wsign-conversion` to enable the warning, or `-Weverything` – llllllllll Dec 05 '18 at 10:22
  • @einpoklum The warning exists in clang 6.0, but not 7.0. likely a bug. – llllllllll Dec 05 '18 at 11:37