Barrett Adair
Barrett Adair

Reputation: 1316

Is noexcept deduction allowed in class template partial specialization?

For the program below, Clang 5 (trunk) reports that IsNoexcept is not deducible, while GCC 7.1 segfaults. What does the standard (draft) say about this? Is this a compiler QOI issue?

static_assert(__cpp_noexcept_function_type, "requires c++1z");

template<typename T>
struct is_noexcept;

template<bool IsNoexcept>
struct is_noexcept<void() noexcept(IsNoexcept)> {
    static constexpr auto value = IsNoexcept;
};

static_assert(is_noexcept<void() noexcept>::value);
static_assert(!is_noexcept<void()>::value);

int main() {}

Relates to proposal P0012.

Upvotes: 9

Views: 859

Answers (2)

T.C.
T.C.

Reputation: 137425

  1. [temp.deduct.type]/8 lists all forms of types from which a template argument can be deduced. The exception-specification is not in the list and hence is not deducible.
  2. As an extension, GCC permits deducing from noexcept to ease the implementation of std::is_function. It looks like the extension is only very lightly tested.
  3. The extension was first suggested by Clang's maintainer and appeared to have some support in the committee, but it's not clear if it will eventually make its way into the standard.
  4. This is not a conforming extension because it changes the meaning of well-defined code, e.g., the value of g(f) with the following snippet:

    void f() noexcept;
    
    template<bool E = false, class R>
    constexpr bool g(R (*)() noexcept(E)){
        return E;
    }
    

Upvotes: 6

AndyG
AndyG

Reputation: 41145

From what I can tell, the value of the expression contained within the noexcept specifier does not become part of a function's type.*

N4618 §8.3.5/1 [dcl.fct] states (emphasis mine)

In a declaration T D where D has the form
     D1 ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt noexcept-specifieroptattribute-specifier-seqopt

and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T”, the type of the declarator-id in D is “derived-declarator-type-list noexceptopt function of (parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt returning T”, where the optional noexcept is present if and only if the exception specification (15.4) is non-throwing. The optional attribute-specifier-seq appertains to the function type.

So this means that the type of a function either includes noexcept or not; if the expression expr within noexcept(expr) evaluates to false, then the function's type will exclude the keyword noexcept altogether.

So you're forced to do something like this instead:

template<typename T>
struct is_noexcept
{
    static constexpr bool value = false;
};

template<>
struct is_noexcept<void() noexcept> {
    static constexpr auto value = true;
};

However, I think that the fact that code like this compiles is misleading:

void (*fp)() noexcept(false);

Because the type of the following function:

void foo() noexcept(false)
{
}

Is void().

And the type of the following function:

void bar() noexcept(true)
{
}

is void() noexcept

BUT, we can do this:

void (*fp)() noexcept(false) = &bar;
fp();

Even though bar is declared noexcept, we're allowed to assign the function pointer to it! So it's misleading; I can't find a standard reference for it, but it appears that the rules allow such conversions implicitly in order to accommodate an incredible amount of backwards compatibility. §5/14.2 [expr] talks about this wrt to composite pointer types.

Thankfully, though, this is illegal:

void (*fp)() noexcept(true) = &foo;

(See the example at N4618 §4.13 [conv.fctptr] for reference).

*integration of exception specifiers into the type system was proposed in N4320 (which was adopted into the C++17 standard)

Upvotes: 1

Related Questions