noexcept and local variables

Is code like this supposed to work?

template <class T>
class A
{
    T t_;
public:
    void swap(A& y) noexcept(noexcept(swap(t_, y.t_)));
};

template <class T>
void
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));

int main()
{
    A<int> i, j;
    swap(i, j);
}

It currently gives errors:

test.cpp:6:49: error: member access into incomplete type 'A<int>'
    void swap(A& y) noexcept(noexcept(swap(t_, y.t_)));
                                                ^
test.cpp:15:12: note: in instantiation of template class 'A<int>' requested here
    A<int> i, j;
           ^
test.cpp:2:7: note: definition of 'A<int>' is not complete until the closing '}'
class A
      ^
test.cpp:11:42: error: reference to local variable 'x' declared in enclosed function 'swap'
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));
                                         ^
test.cpp:11:1: note: in instantiation of function template specialization 'swap<int>' requested here
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));
^
test.cpp:11:12: note: 'x' declared here
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));
           ^
2 errors generated.

I haven't looked at the language side to see if it is supposed to work. But there's library specs that implies it should work. I wouldn't be surprised if the library guys (myself included) just didn't understand the language. And if it isn't supposed to work, I can work around it easily enough.

Here's another variation that works around the first error, but the second remains:

#include <type_traits>

using std::swap;

template <class T>
class A
{
    T t_;
public:
    void swap(A& y) noexcept(noexcept(swap(std::declval<T&>(),
                                           std::declval<T&>())));
};

template <class T>
void
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));

int main()
{
    A<int> i, j;
    swap(i, j);
}

test.cpp:16:42: error: reference to local variable 'x' declared in enclosed function 'swap'
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));
                                         ^
test.cpp:16:1: note: in instantiation of function template specialization 'swap<int>' requested here
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));
^
test.cpp:16:12: note: 'x' declared here
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));
           ^
1 error generated.

Just wanted to check here first in case it was a bug.

Howard

Is code like this supposed to work?

template<class T>
class A
{
     T t_;
public:
     void swap(A& y) noexcept(noexcept(swap(t_, y.t_)));
};

template<class T>
void
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));

int main()
{
     A<int> i, j;
     swap(i, j);
}

It currently gives errors:

test.cpp:6:49: error: member access into incomplete type 'A<int>'
     void swap(A& y) noexcept(noexcept(swap(t_, y.t_)));

Clang bug. This is supposed to work per 9.2p2. I don't know what part of the class is visible at that point, though. That is, if t_ was declared after swap(), would the code still be valid? I don't think it would be, but the standard references for this stuff are all over the place.
(Also, blargh! This will be horrible to fix.)

test.cpp:11:42: error: reference to local variable 'x' declared in enclosed function 'swap'
swap(A<T>& x, A<T>& y) noexcept(noexcept(x.swap(y)));
                                          ^

I don't even understand what that's supposed to mean. Even if this is actually invalid (and I don't think it is), the error message alone must be considered a bug.

Sebastian

Thanks for looking into this Sebastian.

I've checked in libc++ workarounds for this. And I've checked in separate bugzilla's for each case, as it sounds like it may be more practical to fix one than the other:

http://llvm.org/bugs/show_bug.cgi?id=10035
http://llvm.org/bugs/show_bug.cgi?id=10036

Howard

The entire class is in scope, meaning we have to delay calculation of exception specifications until the end of the class - probably using the same technology proposed for in-class member initializers.

There is an obvious issue of recursive or mutually recursive noexcept:

void f() noexcept(noexcept(f()));

struct foo {
   void a() noexcept(noexcept(b()));
   void b() noexcept(noexcept(a()));
};

I've submitted a defect report this about this and other issues encountered with noexcept. I believe we can successfully catch issues of recursion, and that they should be considered ill-formed (potentially no diagnostic required).

Sean

test.cpp:6:49: error: member access into incomplete type 'A<int>'
     void swap(A& y) noexcept(noexcept(swap(t_, y.t_)));

Clang bug. This is supposed to work per 9.2p2. I don't know what part of
the class is visible at that point, though. That is, if t_ was declared
after swap(), would the code still be valid? I don't think it would be,
but the standard references for this stuff are all over the place.
(Also, blargh! This will be horrible to fix.)

The entire class is in scope, meaning we have to delay calculation of
exception specifications until the end of the class - probably using the
same technology proposed for in-class member initializers.

Right. Default function arguments are another, more closely related, case where we need to delay parsing until the class is fully defined.

There is an obvious issue of recursive or mutually recursive noexcept:

void f() noexcept(noexcept(f()));

struct foo {
  void a() noexcept(noexcept(b()));
  void b() noexcept(noexcept(a()));
};

I've submitted a defect report this about this and other issues
encountered with noexcept. I believe we can successfully catch issues of
recursion, and that they should be considered ill-formed (potentially no
diagnostic required).

With default function arguments, the committee decided to consider the program ill-formed if a default argument for a member function declared earlier in the class relies on a default argument declared later in the class. I expect that the committee would do the same here.

  - Doug

I no longer consider the libc++ implementation to have workarounds for this issue. The expression-based implementation was incorrect anyway, at least for where and how I was using it. The noexcept expression in:

template<class T>
class A
{
   T t_;
public:
   void swap(A& y) noexcept(noexcept(swap(t_, y.t_)));
};

can cause std::swap(T&,T&) to be fully instantiated which causes problems for type T that aren't swappable, even if A<T>::swap is never instantiated. When for example T was a non-copyable, non-movable, but default constructible type D, then:

  A<D> a;

would fail to compile because of instantiating std::swap(D&, D&).

I've created __is_swappable<T> and __is_nothrow_swappable<T> to put into the noexcepts everywhere I need to.

Howard

Note that I am entirely in favor of these, which deserved imho to be in the standard. If I may ask: how does your __is_swappable work?

You provide a default low-priority swap overload (shouldn't it be hidden by the way? Leaving it in namespace std looks like it prevents users from reusing the same technique in their code), but there is no SFINAE on the main swap function preventing it from being chosen so it looks like even __is_swappable<__nat>::value is true.

What am I missing?

I've created __is_swappable<T> and __is_nothrow_swappable<T> to put into the noexcepts everywhere I need to.

Note that I am entirely in favor of these, which deserved imho to be in the standard. If I may ask: how does your __is_swappable work?

You provide a default low-priority swap overload (shouldn't it be hidden by the way?

Yes.

Leaving it in namespace std looks like it prevents users from reusing the same technique in their code), but there is no SFINAE on the main swap function preventing it from being chosen so it looks like even __is_swappable<__nat>::value is true.

You are completely correct.

What am I missing?

Not a thing, thanks!

I'll be checking in a fix shortly (mixed in with noexcept for <memory>).

Howard

Hello,

don't you need to also constrain the other swap overloads (say swap(pair,pair) should only exist when the elements are swappable, etc)?

Although it sounds like the latest fix from Sean Hunt makes the whole swappable trait thing unnecessary.

<nod> I have yet to come up to speed in this new language. :slight_smile: Your question is a good one, and I don't know the answer yet.

Howard