Two concatenated templates with each a concept-constraint

Viewed 81

Consider the following code:

#include <iostream>
#include <utility>
#include <type_traits>

using namespace std;

template<typename K>
    requires is_nothrow_move_constructible<K>::value
struct S
{
    using value_type = pair<K, K>;
    struct iterator
    {
    };
    template<typename P>
        requires is_constructible<value_type, P &&>::value
    pair<iterator, bool> insert( P &&x );
};

template<typename K>
    requires is_nothrow_move_constructible<K>::value
template<typename P>
    requires is_constructible<typename S<K>::value_type, P &&>::value
pair<typename S<K>::iterator, bool> S<K>::insert( P &&x )
{
}

int main()
{
    std::cout << "Hello World!\n";
}

Why does this compile with g++ 11.1.0 and not with clang 12.0.0. The clang-error is:

xxx.cpp:24:43: error: out-of-line definition of 'insert' does not match any declaration in 'S<K>'
pair<typename S<K>::iterator, bool> S<K>::insert( P &&x )
                                          ^~~~~~
1 error generated.

clang++ 13.0.0 under Windows does report the same error.

MSVC 19.29.30136 gives the following error:

xxx.cpp
xxx.cpp(25): error C2244: 'S<K>::insert': unable to match function definition to an existing declaration
xxx.cpp(24): note: see declaration of 'S<K>::insert'
xxx.cpp(25): note: definition
xxx.cpp(25): note: 'std::pair<S<K>::iterator,bool> S<K>::insert(P &&)'
xxx.cpp(25): note: existing declarations
xxx.cpp(25): note: 'std::pair<S<K>::iterator,bool> S<K>::insert(P &&)'

Is there a workaround ?

1 Answers

When you have a definition of a function template that is separate from the declaration, the two have to match exactly - be token for token identical (modulo parameter names). In your case, they don't match - you have value_type in one case and typename S<K>::value_type in the other, so this is ill-formed, no diagnostic required.

The easy workaround is: just define the function in-line so you don't have even have to worry about copying the declaration. This definitely works, and is almost always clearer anyway.

If you really want out-of-line definitions, then you need to restructure your declaration so that it allows you to name value_type the same way in both cases:

template<typename K>
    requires is_nothrow_move_constructible_v<K>
struct S
{
    using value_type = pair<K, K>;
    struct iterator
    { };

    template<typename P>
    auto insert( P &&x ) -> pair<iterator, bool>
        requires is_constructible_v<value_type, P &&>;
};

template<typename K>
    requires is_nothrow_move_constructible_v<K>
template<typename P>
auto S<K>::insert( P &&x ) -> pair<iterator, bool>
    requires is_constructible_v<value_type, P &&>
{
}

On the several advantages of the trailing-return-type approach to writing functions is that the class itself is in scope when you're doing name lookup in the contexts after it appears. So in the definition above, iterator and value_type are looked up in S<K>, which allows you to use the same token sequence (and also just lets you avoid repeating them for no reason).


Also, I changed your traits to use the form meow_v<T> instead meow<T>::value, since that's shorter.

Related