How do I write an ADL-enabled trailing return type, or noexcept specification?

Imagine I'm writing some container template or something. And the time comes to specialize std::swap for it. As a good citizen, I'll enable ADL by doing something like this:

template <typename T>
void swap(my_template<T>& x, my_template<T>& y) {
    using std::swap;
    swap(x.something_that_is_a_T, y.something_that_is_a_T);
}

This is very neat and all. Until I want to add an exception specification. My swap is noexcept as long as the swap for T is noexcept. So, I'd be writing something like:

template <typename T>
void swap(my_template<T>& x, my_template<T>& y)
    noexcept(noexcept(swap(std::declval<T>(), std::declval<T>())))

Problem is, the swap in there needs to be the ADL-discovered swap or std::swap. How do I handle this?


Solution 1:

I think I would move it into a separate namespace

namespace tricks {
    using std::swap;

    template <typename T, typename U>
    void swap(T &t, U &u) noexcept(noexcept(swap(t, u)));
}

template <typename T>
void swap(my_template<T>& x, my_template<T>& y)
  noexcept(noexcept(tricks::swap(std::declval<T>(), std::declval<T>()))) 
{
    using std::swap;
    swap(x.something_that_is_a_T, y.something_that_is_a_T);
}

Alternatively you can move the whole code up into tricks and delegate to there.

Solution 2:

There is a similar problem for return types:

// Want to be compatible with both boost::tuple and std::tuple
template<typename Tuple>
auto first(Tuple&& tuple)
-> /* ??? */
{
    // Introduce name into scope
    using std::get;
    // but ADL can still pick boost::get for boost::tuple
    return get<0>(std::forward<Tuple>(tuple));
}

Using decltype( get<0>(std::forward<Tuple>(tuple)) ) isn't correct as get isn't in scope.

Possible workarounds are:

  • Introducing a dummy template (get in my example, swap in your case) in the enclosing scope; this includes putting the using std::swap declaration in the enclosing namespace, with the drawback of polluting the namespace.

  • Use of a type trait: typename std::tuple_element<0, typename std::remove_reference<Tuple>::type>::type (actually this one is problematic but for reasons that don't belong here) in my example, and a potential is_nothrow_swappable<T>::value in your case. Specializations then allow the template to be extended for other types if need be.

Solution 3:

Rather than declaring but not defining a function template, which seems likely to cause confusion, I would write my own type trait (which is what should probably be in the standard library, anyway). Following the lead of the standard library, I would define something like the following:

#include <type_traits>
#include <utility>

namespace adl {

using std::swap;

template<typename T, typename U>
struct is_nothrow_swappable : std::integral_constant<
    bool,
    noexcept(swap(std::declval<T &>(), std::declval<U &>()))
> {
};

}   // namespace adl

We have to define our own namespace to import std::swap into (to avoid giving it to everyone), but of course, if it were in the standard library that wouldn't be necessary because they can already make unqualified calls to swap.

Solution 4:

C++17 has solved this particular use case with std::is_nothrow_swappable: http://en.cppreference.com/w/cpp/types/is_swappable