noexcept and static_assert

682 views
Skip to first unread message

denis bider

unread,
Jul 23, 2015, 4:19:46 PM7/23/15
to ISO C++ Standard - Future Proposals
It is fairly awkward that I have to do this:


#define NoExcept(EXPR) \

    ([&]() { static_assert(noexcept(EXPR), "Expression can throw"); }, (EXPR))



in order to be able to do this:


Vect<T>& Swap(Vect<T>& x) noexcept

    { NoExcept(ContainerSwap(x)); return *this; }



There should be an easier way to use the noexcept operator with static_assert.


It is furthermore awkward that static_assert must be a statement, instead of an expression, requiring the above lambda construction.

It's 2015, why are we still making statements that can't serve as expressions...?



Arthur O'Dwyer

unread,
Jul 23, 2015, 8:19:14 PM7/23/15
to ISO C++ Standard - Future Proposals, isocp...@denisbider.com
On Thursday, July 23, 2015 at 1:19:46 PM UTC-7, denis bider wrote:
It is fairly awkward that I have to do this:


#define NoExcept(EXPR) \

    ([&]() { static_assert(noexcept(EXPR), "Expression can throw"); }, (EXPR))



in order to be able to do this:


Vect<T>& Swap(Vect<T>& x) noexcept

    { NoExcept(ContainerSwap(x)); return *this; }



There should be an easier way to use the noexcept operator with static_assert.

Have you tried

    Vect<T>& Swap(Vect<T>& x) noexcept
    {
        static_assert(noexcept(ContainerSwap(x)));
        ContainerSwap(x);
        return *this;
    }

?

Alternatively, just mark ContainerSwap as noexcept (assuming you control its code). It definitely shouldn't be throwing anything.

Alternatively, just forget about marking things noexcept and move on. I have yet to see any in-game effect of noexcept, except for the std::vector::resize misfeature (that it uses move-construction iff you've marked your move-constructor noexcept, and otherwise falls back to slow copy-construction).

–Arthur

denis bider

unread,
Jul 23, 2015, 9:24:38 PM7/23/15
to ISO C++ Standard - Future Proposals, arthur....@gmail.com
You suggest code duplication, which is exactly what I'm trying to avoid. :)

The thing about exception safety is that you never see effects if it's done properly.

I need noexcept because my container implementation requires is_nothrow_move_constructible and is_nothrow_destructible to provide strong exception safety. I have already caught a bug this way - assuming an STL class was nothrow move constructible, and it turns out, with my compiler, it isn't.

The macro is neat, and it works great. But it shouldn't have to be a macro. I am genuinely befuddled why the noexcept operator does not already work this way. It should pass through the expression result, or static assert if the expression can throw.

David Krauss

unread,
Jul 23, 2015, 10:37:00 PM7/23/15
to std-pr...@isocpp.org
On 2015–07–24, at 9:24 AM, denis bider <isocp...@denisbider.com> wrote:

You suggest code duplication, which is exactly what I'm trying to avoid. :)

The thing about exception safety is that you never see effects if it's done properly.

The noexcept operator is associated with duplication in more situations. A very common idiom is duplication of a function’s body into its exception specification using noexcept.

void foo() noexcept( dosomething() )
    { return dosomething(); }

The solution is to introduce noexcept(auto):

void foo() noexcept(auto)
    { return dosomething(); }

This potentially solves at least some cases of duplication in unconditionally noexcept functions.

Given sufficiently relaxed rules we could have this:

void foo() noexcept(auto) {
    return dosomething();
    // The "noexcept(auto)" could be considered resolved after the last evaluated expression in the function.
    static_assert( noexcept( foo() ) );
}

Alternatively (and perhaps better), a helper function could encapsulate the duplication:

template< typename fn, typename ... arg >
decltype(auto) invoke_require_noexcept( fn && f, arg && ... a ) noexcept {
    static_assert( noexcept( std::invoke( std::forward< fn >( f ), std::forward< arg >( a ) ... ) ) );
    return std::invoke( std::forward< fn >( f ), std::forward< arg >( a ) ... );
}

void foo() noexcept
    { return invoke_require_noexcept( []() noexcept(auto) { dosomething() } ); }

Given noexcept(auto), perhaps it be the default for lambda expressions.

Thiago Macieira

unread,
Jul 23, 2015, 10:38:13 PM7/23/15
to std-pr...@isocpp.org
On Thursday 23 July 2015 18:24:38 denis bider wrote:
> The macro is neat, and it works great. But it shouldn't *have* to be a
> macro. I am genuinely befuddled why the noexcept operator does not already
> work this way. It should pass through the expression result, or static
> assert if the expression can throw.

if (noexcept(foo())
bar();
else
baz();

The above should neither call foo ("pass through the expression result") nor
static assert.

The above is provided so that bar() could be an specialised version of baz()
for when the expression is noexcept.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Thiago Macieira

unread,
Jul 23, 2015, 10:56:20 PM7/23/15
to std-pr...@isocpp.org
On Friday 24 July 2015 10:36:52 David Krauss wrote:
> The noexcept operator is associated with duplication in more situations. A
> very common idiom is duplication of a function’s body into its exception
> specification using noexcept.
>
> void foo() noexcept( dosomething() )
> { return dosomething(); }
>
> The solution is to introduce noexcept(auto):
>
> void foo() noexcept(auto)
> { return dosomething(); }

We discussed this before, but refresh my mind please: did we agree that
noexcept(auto) should be the default for inline functions?

If the entire body of a function is noexcept, it can't throw. If the function
is inline, then changing it so it later throws but not recompiling everything
is an ODR violation. The standard wouldn't care.

For ABI binary compatibility, the only case that would be problematic is when:
a) the function was inline and thus implicitly noexcept
b) the function was called but not inlined
c) the out-of-line copy is subject to merging across multiple libraries
d) the function was de-inlined and made to throw
e) the dynamic linker chose to call the copy that throws

I find that to be very unlikely to happen, for the following reasons:
1) inline functions get often inlined
2) a good portion of C++ libraries don't try to keep binary compatibility
(boost doesn't even try, for example)
3) of those that do, deinlining is a very uncommon thing to do
4) of those that do keep binary compatibility, a good chunk follow Qt's
paradigm and don't use exceptions
5) on modern operating systems with modern C++ shared libraries that try to
keep binary compatibility, out-of-line copy of inline functions are not
merged across shared library boundaries (-fvisibility-inlines-hidden; for
MSVC, this applies too, except for debug-mode calls to inline member
functions in classes that are __declspec(dllexport)).
Reply all
Reply to author
Forward
0 new messages