Why does std::swap<std::array<int,3>> not compile?
Asked Answered
O

3

21

I would like to swap two arrays of ints of some fixed size.

Counterintuitively, the following does not compile because no matching instance of swap has been found.

#include <array>
#include <utility>

int main()
{
    std::array<int,3> a, b;
    std::swap< std::array<int,3> >( a, b ); 
    return 0;
}

I find this surprising. However, replacing the swap with std::swap(a,b) compiles and (according to VSCode) has signature

inline void std::swap<int, 3UL>(std::array<int, 3UL> &__one, std::array<int, 3UL> &__two)

which I cannot make sense of either.

Q: What is going on here?

Organism answered 19/1 at 9:3 Comment(5)
Fundamentally, std::swap is an overloaded function, not a function template. There is therefore no reason to assume that std::swap(T&, T&) (for some type T) is callable as std::swap<T>(x, y). — Of course if you want to implement an overload for a type template (such as std::array) you need to make the overload a template, too. But, again, it won’t be templated on T; it will be templated on the template arguments of your type template. — Lastly, for generic types T you should not call std::swap via std::swap(x, y)! Instead, do using std::swap; swap(x, y);Katushka
std::swap being an overloaded function does not contradict it being a function template. Language standard explicitly lists std::swap variants along with their requirements. Basic variant template<class T> void swap( T& a, T& b ); must be usable as long as T meets the requirements for MoveConstructible and MoveAssignable. So there is no reason to assume that std::swap<std::array<int,3>> is not callable.Infrequent
libstdc++ issue submitted: gcc.gnu.org/bugzilla/show_bug.cgi?id=113522Amenra
Because swap can be overfloaded and not all overloads use templates the same way, the recommended way to call it is using std::swap; swap(a, b);. This also allows for argument-dependent lookup (ADL) to find swap functions outside of namespace std.Ician
The code compiles with MSVC++ V19. It does not compile with gcc or clang, however.Kaput
C
17

The overload you are looking for is (from cppreference):

template< class T, std::size_t N >    
constexpr void swap( std::array<T, N>& lhs,
                     std::array<T, N>& rhs )
                         noexcept(/* see below */);

As the error reports, the compiler cant find a viable overload of std::swap() that matches std::swap<std::array<int,3>>.

This would be the "right" way to supply template arguments explictly:

#include <array>
#include <utility>

int main()
{
    std::array<int,3> a, b;
    std::swap<int,3>( a, b ); 
    return 0;
}

I doubt there is a situation where you actually want to do that though.

PS: You can also see that in the signature you get from VSCOde: std::swap<int, 3UL> is not std::swap<std::array<int,3UL>>. However, looking at implementation can be misleading sometimes and I rather suggest to consult documentation first.

Creditor answered 19/1 at 9:7 Comment(4)
You might want to explain why std::swap(T&, T&) is not a good match when T is std::array.Godin
@RemyLebeau I saw your edit and I am afraid I have to do some reading before I can write it down properlyCreditor
@RemyLebeau actaully I am confused by your edit. To my understanding std::swap(T&,T&) is viable, but std::swap(arra<T,N>&) is a better match because its more specialized. Also after rereading overload resolution I come to the same conclusion. I suppose you wouldnt have made the edit when you didnt know what you are talking about, but I dont see it (on the other hand the original was definitely wrong)Creditor
@RemyLebeau and actually I now do not understand anymore why OPs code didnt compile myself :D how embarrassingCreditor
I
10

The problem seem to be caused by libstdc++ implementation. It enforces tuple-like requirement on types used in suitable "basic" variant of std::swap (source):

  template<typename _Tp>
    _GLIBCXX20_CONSTEXPR
    inline
#if __cplusplus >= 201103L
    typename enable_if<__and_<__not_<__is_tuple_like<_Tp>>,
                  is_move_constructible<_Tp>,
                  is_move_assignable<_Tp>>::value>::type
#else
    void
#endif
    swap(_Tp& __a, _Tp& __b)

even though standard does not mention such a restriction:

20.2.2
swap [utility.swap]
template<class T>
constexpr void swap(T& a, T& b) noexcept(see below );
1 Remarks: This function is a designated customization point (16.5.4.2.1) and shall not participate in overload resolution unless is_move_constructible_v<T> is true and is_move_assignable_v<T> is true. The expression inside noexcept is equivalent to: is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>
2 Requires: Type T shall be Cpp17MoveConstructible (Table 26) and Cpp17MoveAssignable (Table 28).

code works with vc++ where library only checks for is_move_constructible and is_move_assignable, as required by standard.

Infrequent answered 19/1 at 9:33 Comment(7)
You are looking in the wrong place: the overload from [utility.swap] is not the one that is supposed to be called for std::array arguments. You’ll find the relevant overload in [array.special].Katushka
@KonradRudolph OP manually supplies template parameter, so array-targeting overload [array.syn] won't match anyway and only basic variants may be suitable.Infrequent
Right, and they shouldn’t have done that. I guess the wording you quote could be understood as saying that this overload should be callable on any type that’s move constructible and move assignable. However, I think the intent is pretty clearly “… unless a more specialised overload exists”, so I think the behaviour observed by OP is standards compliant.Katushka
@KonradRudolph Sorry, but i see no intent like “… unless a more specialized overload exists” at all. array satisfies all the listed requirements for being move constructible and move assignable so there is not a single reason for basic overload not to accept it, even if an array-specific variant exists. This basic overload is only broken for tuple-like types, and properly works for other suitable types, such as std::string even though there is a string-specific variant. –Infrequent
And, it also works with libc++: godbolt.org/z/jTPh9xEEb. I think it's a libstdc++ bug (unless there is something in the standard that says that the generic template should not be called for tuple-like types; but I can't find any such requirements there).Spinach
@Infrequent Fair enough. It’s clearly not the intent that this overload be called with arguments of type std::array, but it should probably work if that overload is specifically selected (the way OP did it or via a pointer to function).Katushka
I don't consider this a bug, the code is silly and there is absolutely no reason to use an explicit template argument list. Just use the correct overload of swap, the way it's supposed to be used.Trichocyst
D
0

See https://timsong-cpp.github.io/lwg-issues/2766 wich is still an open defect against the C++ standard (library part)..

Deroo answered 20/1 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.