7

On C++ Primer on noexcept exception specification, it is said that a pointer to a function that may throw Implicitly (defined without exception specification e.g: void(*p)();) or explicitly (void(*p)() noexcept(false);) may point to any function even to a non-throwing function.

On the other hand a function pointer which may not throw (noexcept e.g void(*p) noexcept;) can only point to a function that won't throw.

I found that very logical because the first pointer it is OK to point to a non-throwing function from a throwing function pointer and the second too is so logical.

I've tried this to understand more:

void func1(){ // may throw
    std::cout << "func1()\n";
}

void func2() noexcept(false){ // may throw
    std::cout << "func2()\n";
}

void func3() noexcept(true){ // won't throw
    std::cout << "func3()\n";
}

void func4() noexcept{ // won't throw
    std::cout << "func4()\n";
}


int main(int argc, char* argv[]){

    void(*pFn1)();
    pFn1 = func1; // OK
    pFn1 = func2; // OK
    pFn1 = func3; // OK
    pFn1 = func4; // OK

    void(*pFn2)() noexcept(false);
    pFn2 = func1; // OK
    pFn2 = func2; // OK
    pFn2 = func3; // OK
    pFn2 = func4; // OK

    void(*pFn3)() noexcept(true);
    pFn3 = func1; // Error on C++ 17 and above. OK on C++11 and 14
    pFn3 = func2; // Error on C++ 17 and above. OK on C++11 and 14
    pFn3 = func3; // OK
    pFn3 = func4; // OK

    void(*pFn4)() noexcept(true);
    pFn4 = func1; // Error on C++ 17 and above. OK on C++11 and 14
    pFn4 = func2; // Error on C++ 17 and above. OK on C++11 and 14
    pFn4 = func3; // OK
    pFn4 = func4; // OK

    std::cout << '\n';
}
  • When I compile the program against -std=c++17, -std=c++2a it works as it should so I get the errors as I've written in the lines comments. But when I compile against -std=c++11, -std=c++14 I get them all work and the compiler doesn't complain?!

Does this mean the standard has changed? Thank you!

Maestro
  • 2,512
  • 9
  • 24
  • 3
    It was probably unspecified in C++14 and below. Then somebody noticed the discrepancy, and they tightened up the requirements in C++17. – Martin York Mar 10 '21 at 23:37
  • 1
    If you know what section of the standard your quote is coming from you can compare it against previous versions of the standard to see if it exists. – Martin York Mar 10 '21 at 23:37
  • 4
    Looks like a GCC bug, [Clang and MSVC reject it](https://godbolt.org/z/P88s4n), and according to [cppreference](https://en.cppreference.com/w/cpp/language/noexcept_spec#Explanation) it should be an error even before C++17. – IlCapitano Mar 10 '21 at 23:43
  • 1
    This wordage (with a quick binary chop) seems to have been added in `2015-11-09` to [N4567](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4567.pdf) the previous version [n4527](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4527.pdf) does not seem to have that wording. I found it in the section `[conv.fctptr]` – Martin York Mar 10 '21 at 23:50
  • 2
    Looks like in C++14, the noexcept-ness was not part of the function type, so the declarations of `pFn1`, `pFn2`, `pFn3`, and `pFn4` are all equivalent except for the identifier. ([\[dcl.fct\]](https://timsong-cpp.github.io/cppwp/n4140/dcl.fct), especially paragraphs 1-2 and the last normative sentence in 6) – aschepler Mar 10 '21 at 23:51
  • @IlCapitano: I think you are correct because running the same program on Clang 7.0 with `-std=c++11` it doesn't compile but on GCC it compiles so I think it is a bug in GCC. Please add it as an answer. – Maestro Mar 11 '21 at 19:14

1 Answers1

2

It's ill-formed in C++17 because the noexcept is part of the type system, and there is no conversion from a pointer to a potentially-throwing function to a pointer to a non-throwing function.

Before C++17 the relevant rule is [except.spec] p5:

A similar restriction applies to assignment to and initialization of pointers to functions, pointers to member functions, and references to functions: the target entity shall allow at least the exceptions allowed by the source value in the assignment or initialization.

So although the noexcept isn't part of the type system, the OP's assignments are ill-formed.

G++ does not give an error for the example in p5:

class A { /* ... */ };
void (*pf1)(); // no exception specification
void (*pf2)() throw(A);

void f() {
  pf1 = pf2; // OK: pf1 is less restrictive
  pf2 = pf1; // error: pf2 is more restrictive
}

That should be ill-formed in C++98/11/14.

The equivalent with a noexcept-specifier would be:

class A { /* ... */ };
void (*pf1)(); // no exception specification
void (*pf2)() noexcept;

void f() {
  pf1 = pf2; // OK: pf1 is less restrictive
  pf2 = pf1; // error: pf2 is more restrictive
}

G++ doesn't reject this in 98/11/14, only C++17 and up.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521