Bug 57632 - diagonistic for different exception specifier/noexcept if decl is declared twice should be improved
Summary: diagonistic for different exception specifier/noexcept if decl is declared tw...
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.8.2
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic
Depends on:
Blocks:
 
Reported: 2013-06-17 05:51 UTC by Bas Vodde
Modified: 2022-01-09 05:43 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2022-01-07 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Bas Vodde 2013-06-17 05:51:08 UTC
G++ on MacOsX acts different when enabling the new c++11 related to operator new overloads. If I compile the following code:

#include "stdlib.h"
#include <new>

void* operator new(size_t mem) throw(std::bad_alloc);
void* operator new(size_t mem) throw(std::bad_alloc);

Then when compiling g++ new.cpp, it compiles fine.
But when I compile g++ -std=c++11 new.cpp, then it results in this error:

new.cpp:6:52: error: declaration of ‘void* operator new(size_t) throw (std::bad_alloc)’ has a different exception specifier
 void* operator new(size_t mem) throw(std::bad_alloc);
                                                    ^
new.cpp:5:7: error: from previous declaration ‘void* operator new(std::size_t)’
 void* operator new(size_t mem) throw(std::bad_alloc);
       ^
------------

I've not been able to replicate this under linux and digging into it a bit, it seems to relate to the c++config.h definitions of _GLIBCXX_THROW.
Comment 1 Paolo Carlini 2013-06-17 09:45:11 UTC
This happens on Linux too. The issue is that per C++11 (see 18.6) operator new is declared in <new> as:

  void* operator new(size_t mem);                  (1)

and as such is internally pre-declared in decl.c:cxx_init_decl_processing. Then, if I understand correctly, when the parser sees in user code:

  void* operator new(size_t mem) throw(std::bad_alloc);

it thinks, Ok the user is just redeclaring (1), it simply ignores the exception specifier. Then the additional declaration in user code is seen inconsistent with the former one (it becomes clear that the exception specifier was dropped the first time).

We (used to) have a completely similar, dual, issue in C++98 for this user code:

void* operator new(std::size_t mem);
void* operator new(std::size_t mem);

and I'm not sure whether and how we want to do better.

Note that changing in C++11 the user code to:

void* operator new(std::size_t mem);
void* operator new(std::size_t mem);

is perfectly fine.
Comment 2 Daniel Krügler 2013-06-17 20:09:07 UTC
There exists a related core language issue for this:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#967

Originally it was submitted when these function had an exception specification, but in C++11 the situation is different. Nonetheless I believe that this issue might influence whether this will turn out to be a gcc bug or not.
Comment 3 Bas Vodde 2013-06-18 01:22:19 UTC
Thanks for the comments. I understand the problems in implementing a compiler, when this is also unclear in the language itself.

Whatever is decided related to this, it would probably be a good idea to give a better error message. Right now, the error message is telling the user that the operator new *with* an exception specifier doesn't have one and that they are different, even though the two lines are exactly the same. It is a bit confusing.

It would be good though to solve this in a way that both works with C++11 and C++03 as it came up when compiling a piece of code that is used by multiple compilers and by both language versions.
Comment 4 Andrew Pinski 2022-01-07 05:49:49 UTC
So the defect report against C++ was closed as not a defect:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#967 .
The related one http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#1948 dealing with noexcept was also closed as not a defect.


So GCC is correct, There is no bug here.
Comment 5 Jonathan Wakely 2022-01-07 12:47:12 UTC
I agree with comment 3 that the current diagnostic is poor:

#include <new>
void* operator new(std::size_t mem) throw(std::bad_alloc);
void* operator new(std::size_t mem) throw(std::bad_alloc);


g++ -std=c++11  -c new.cc -Wno-deprecated
new.cc:3:7: error: declaration of ‘void* operator new(std::size_t) throw (std::bad_alloc)’ has a different exception specifier
    3 | void* operator new(std::size_t mem) throw(std::bad_alloc);
      |       ^~~~~~~~
new.cc:2:7: note: from previous declaration ‘void* operator new(std::size_t)’
    2 | void* operator new(std::size_t mem) throw(std::bad_alloc);
      |       ^~~~~~~~


Why does it accept the incorrect exception specification on line 2, but then give an error for an identical one on line 3? Why do we refer to line 2 in the note and say it's different, when it's not?

Paolo's explanation in comment 1 doesn't make the behaviour correct, it just explains why we behave like that. Either both redeclarations should be valid or neither should be valid.

So I think either we should either:

- accept both redeclarations (as Clang does), or

- give a diagnostic (maybe a pedwarn) for the first redeclaration because it doesn't match the one in <new> (and the implicit one predefined by the compiler), and fix the diagnostic to refer to the previous declaration in <new> instead of the one on line 2.
Comment 6 Andrew Pinski 2022-01-07 23:47:30 UTC
(In reply to Jonathan Wakely from comment #5)
> I agree with comment 3 that the current diagnostic is poor:

Oh I missed that.

> 
> #include <new>
> void* operator new(std::size_t mem) throw(std::bad_alloc);
> void* operator new(std::size_t mem) throw(std::bad_alloc);
> 
> 
> g++ -std=c++11  -c new.cc -Wno-deprecated
> new.cc:3:7: error: declaration of ‘void* operator new(std::size_t) throw
> (std::bad_alloc)’ has a different exception specifier
>     3 | void* operator new(std::size_t mem) throw(std::bad_alloc);
>       |       ^~~~~~~~
> new.cc:2:7: note: from previous declaration ‘void* operator new(std::size_t)’
>     2 | void* operator new(std::size_t mem) throw(std::bad_alloc);
>       |       ^~~~~~~~
> 

Reduced testcase for that:
void f(void);
void f(void) throw(int);
void f(void) throw(int);

Noexcept has the same issue:
void g(void);
void g(void) noexcept;
void g(void) noexcept;

I suspect what happens is the following when we merge the two decls we chose the new decl for the location but we remove the exception specifier/noexcept (confirmed by swapping the first two decls and seeing the error again).
Comment 7 Andrew Pinski 2022-01-08 03:38:00 UTC
(In reply to Andrew Pinski from comment #6)
> Reduced testcase for that:
> void f(void);
> void f(void) throw(int);
> void f(void) throw(int);
> 
> Noexcept has the same issue:
> void g(void);
> void g(void) noexcept;
> void g(void) noexcept;

It is even worse when the first declaration is in a system header file, the second declaration does not cause an error during merging but then we lose the throw/noexcept but have the line number of the second decl so when we go and merge in the third decl, we do the error message that way.