3

With gcc 7.1.0 std::mem_fn is able to detect noexcept-ness on a member function pointer. How does it do this? I thought the noexcept specifier was not a part of the function type?


What is even more confusing is when I remove the noexcept specifier on one method from https://wandbox.org/permlink/JUI3rsLjKRoPArAl all the noexcept values change as seen here https://wandbox.org/permlink/yBJ0R4PxzAXg09ef. And when one is noexcept, all are noexcept. How? Is this a bug?

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Curious
  • 20,870
  • 8
  • 61
  • 146
  • 2
    (By the way, `noexcept` *is* part of the function type in C++17.) – Kerrek SB Oct 17 '17 at 00:06
  • @KerrekSB but cppreference says it is not here http://en.cppreference.com/w/cpp/language/noexcept_spec? – Curious Oct 17 '17 at 00:07
  • From c++17 noexcept is part of the function type. http://en.cppreference.com/w/cpp/language/noexcept_spec – Robert Andrzejuk Oct 17 '17 at 00:19
  • @RobertAndrzejuk but what about cppreference saying otherwise? Also in that case why has the declaration of `mem_fn` not changed accordingly in that case? – Curious Oct 17 '17 at 00:20
  • @ildjarn how? could you give an example with a member function pointer? `mem_fn` accepts one argument, and that does not include `noexcept` in its type, then how does the type system deduce whether or not the pointer is pointing to a noexcept method or not? – Curious Oct 17 '17 at 00:22
  • @ildjarn Hmm, getting slightly confused now. 1. If noexcept is indeed a part of the type system then why not add another overload to `mem_fn` to detect whether the member function pointer is noexcept or not. and 2. the noexcept operator operates at compile time, how is it not a part of the type system? – Curious Oct 17 '17 at 00:26
  • @ildjarn how does it already work? Very confused now. And also 3. Why does the cppreference link i mention say that `noexcept` is not a part of the function type since C++17? – Curious Oct 17 '17 at 00:28
  • 1
    It says it's not part of the type _until_ C++17. If you're having issues reading, I can't help you there. >_> (I already edited the 'how' into a previous comment.) – ildjarn Oct 17 '17 at 00:29
  • 1
    Your example code behaves differently in GCC 7.2 for `c++14` and `c++1z` conformance modes. The question about C++14 is still interesting! – Kerrek SB Oct 17 '17 at 00:33
  • @ildjarn Maybe I am having issues reading.. Could you give an example of how you could achieve this in C++17? – Curious Oct 17 '17 at 00:33
  • @KerrekSB yes! It doesn't even compile for C++1z with gcc 7.1.0 :( would be happy to submit a bug report but I don't even know what the bug is... – Curious Oct 17 '17 at 00:35
  • The C++17-behaviour in GCC 7.2 and HEAD seems to be as expected, so no bug there. As for C++14, `INVOKE` goes via the member pointer, which (in C++14) carries no noexceptness information, so the result of `mem_fn` is not required to provide a noexcept call. But implementations are free to *add* `noexcept`, so neither choice is wrong. – Kerrek SB Oct 17 '17 at 00:38
  • @KerrekSB You are able to compile my code with c++1z? I was not able to do so.. Also the declaration of `mem_fn` since c++17 does not have a noexcept overload, then how does it detect this information from the user? Do you mean that the implementation has secretly added this in, even though it is not mentioned in cppreference, the one true source of standard information stuff? – Curious Oct 17 '17 at 00:40
  • @KerrekSB but then if you look at the two links in my question. As soon as I change the noexcept-ness of one function both change, how does that happen? – Curious Oct 17 '17 at 00:41
  • @Curious: In 7.2, yes: https://wandbox.org/permlink/cUvpMuNnzUtUTsNd – Kerrek SB Oct 17 '17 at 00:47
  • @KerrekSB I'm so very confused rn. no idea what is going on gcc 7.1.0 bugs out af – Curious Oct 17 '17 at 00:51
  • 1
    @Curious : After actually looking into it I see that I was wrong. Apologies for the noise and any confusion caused; I've removed the misleading comments. (EDIT: And only now I see Jon has already answered your question. :-P) – ildjarn Oct 17 '17 at 11:41
  • @ildjarn no problem! Thanks for the discussion : ) – Curious Oct 17 '17 at 15:08

1 Answers1

6

The behaviour you're seeing with GCC in C++14 mode is a bug, see https://gcc.gnu.org/PR77369

In the comments you ask:

Also the declaration of mem_fn since c++17 does not have a noexcept overload, then how does it detect this information from the user?

It doesn't need to have a noexcept overload, consider this C++17 code:

template<typename T>
  constexpr bool is_noexcept_callable(T t) {
    return noexcept(t());
  }

void f() noexcept { }
void g() { }
static_assert( is_noexcept_callable(f) );
static_assert( !is_noexcept_callable(g) );

There's no need to have a noexcept overload of is_noexcept_callable because in C++17 the exception-specification is part of the type, so all the information is already encoded in the type T. When you instantiate std::mem_fn with a pointer to a noexcept(true) function it knows that invoking that function can't throw. When you instantiate it with a noexcept(false) function it knows it might throw.

Finally, the reason your example doesn't compile in C++17 mode with GCC 7.1 is https://gcc.gnu.org/PR80478 which is fixed in GCC 7.2 (so upgrade your compiler).

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Thanks! I had a feeling this was a bug in the compiler! Maybe I am really misunderstanding something, but when you pass a pointer to a member function that erases all compile time information about whether or not that method was noexcept before that right? In this case you have templated the `is_noexcept_callable` so that information is still intact in the type system – Curious Oct 17 '17 at 15:04
  • No, as stated repeatedly in the comments above, _"when you pass a pointer to a member function that erases all compile time information about whether or not that method was noexcept before that"_ is not true in C++17. If you have a pointer to member function of type `void (X::*)() noexcept` then the information isn't erased. If you implicitly convert that to `void (X::*)()` (which is allowed) then you've lost that information. – Jonathan Wakely Oct 17 '17 at 15:30
  • Member function pointers can change at runtime right? How do you keep track of the noexcept-ness of the methods in C++17? – Curious Oct 17 '17 at 15:33
  • Because the noexcept-ness **is part of the type**. As people keep telling you. `void (X::*)() noexcept` is a different type to `void (X::*)()`. You can change the value of a `void(X::*)() noexcept` to point to a different member, but you can't change its type, it's always a pointer to a noexcept member function. – Jonathan Wakely Oct 17 '17 at 15:33
  • `std::mem_fn` tracks only the non noexcept version of a pointer though? It doesn't seem to have two overloads (one for noexcept and another for not). `mem_fn` just accepts a pointer to a member (without any `noexcept` specifications), whether or not that member is noexcept or not is lost right there? – Curious Oct 17 '17 at 15:36
  • No, it holds any pointer to member function. If you pass it a pointer to a noexcept member function then it holds a pointer to a noexcept member function. You don't need a second overload for that. Read my answer again, and again, and again. Do you see two overloads of `is_noexcept_callable`? No. Do you see how it can be called with different types, and so give different answers? Now consider `template auto mem_fn(T t) { return _Mem_fn{t}; }` and think about it. If `T` is a pointer to a noexcept function, then `_Mem_fn` holds a pointer to a noexcept function. No 2nd overload. – Jonathan Wakely Oct 17 '17 at 15:39
  • Or in other words, this is 100% wrong, and is the source of your confusion: _"`std::mem_fn` tracks only the non noexcept version of a pointer though?"_ That is wrong. – Jonathan Wakely Oct 17 '17 at 15:40
  • 1
    I had a horrible misunderstanding of member pointers (and thinking about it, my line of thought makes 0 sense). I was assuming that the T http://en.cppreference.com/w/cpp/utility/functional/mem_fn is just the return type of the function, it contains all the function type information (including the function parameters and the noexceptness of the function). Sorry about that! – Curious Oct 17 '17 at 15:47
  • If it helps https://wandbox.org/permlink/SXNeh9vT08G0cBt2 this was how I understood what the `T` meant, before I was matching that to just store the return type of a method. Since the syntax to initialize a member function pointer is `T Class::*ptr (Args...)` – Curious Oct 17 '17 at 15:50