Timo
Timo

Reputation: 9835

gcc and clang throw "no matching function call" but msvc (cl) compiles and works as expected

I have written a small function template that joins different containers in a new container:

#include <vector>
#include <unordered_set>
#include <string>
#include <iostream>
#include <iterator>

namespace impl
{
    template <typename OutIterator, typename Container, typename ...Containers>
    void join(OutIterator iterator, const Container& container, const Containers& ...containers)
    {        
        for (const auto& item : container)
            *iterator++ = item;

        join(iterator, containers...);  // gcc and clang cannot resolve this call
    }

    template <typename OutIterator, typename Container>
    void join(OutIterator iterator, const Container& container)
    {        
        for (const auto& item : container)
            *iterator++ = item;
    }
}

template <typename OutContainer, typename ...Containers>
OutContainer join(const Containers& ...containers)
{
    OutContainer container;
    auto it = std::inserter(container, container.end());
    impl::join(it, containers...);
    return container;
}

int main()
{
    using namespace std;
    vector<string> a = {"one"s, "two"s, "three"s};
    unordered_set<string> b = {"four"s, "five"s };
    auto res = join<unordered_set<string>>(a, b);

    for (auto& i : res)
        cout << i << "\n";

    return 0;
}

Using MSVC (cl.exe) with /std:c++17 the code compiles and works just fine. But when compiling with clang-6.0 or gcc-7.3, a compiler error is thrown. I.e. gcc says

no matching function for call to 'join(std::insert_iterator<std::unordered_set<std::__cxx11::basic_string<char> > >&)'

So obviously a function with this signature is not defined. But I don't get why it tries to call such a function. Shouldn't it be resolved like this

// in main()
join<unordered_set<string>>(a, b);

unordered_set<string> join(const vector<string>& a, const unordered_set<string>& b);
void impl::join(std::insert_iterator<unordered_set<string>> iterator, const vector<string>& a, const unordered_set<string>& b);
void impl::join(std::insert_iterator<unordered_set<string>> iterator, const unordered_set<string>& b);

Why does gcc try to instantiate join(std::insert_iterator<std::unordered_set<std::__cxx11::basic_string<char>>>&) ?

Here is an example using the compiler explorer.

Upvotes: 7

Views: 1548

Answers (1)

Barry
Barry

Reputation: 303337

gcc and clang are correct. MSVC still has issues with proper template name lookup (i.e. "two-phase lookup").

join, in join(iterator, containers...), is a dependent name. The candidates for finding that name are:

  • All names at the point of the template definition. This lookup just finds itself (the variadic overload), not the other overload (the 2-arg one), because it hasn't been declared yet.
  • All names that can be found by ADL on its arguments. None of those arguments have impl as an associated namespace, so that wouldn't find the other overload either.

In this situation, the fix is trivial: just reorder the two join() overloads. This ensures that the 2-arg join() will be found by that first bullet point.


Note that in C++17, you don't even need two overloads. Just one is fine:

template <typename OutIterator, typename Container, typename... Containers>
void join(OutIterator iterator, Container const& container, Container const&... containers) {
    for (auto const& item : container) {
        *iterator++ = item;
    }

    if constexpr(sizeof...(Containers) > 0) {
        join(iterator, containers...);
    }
}

Also consider using std::copy() instead of the loop. Which would actually allow:

template <typename OutIterator, typename... Containers>
void join(OutIterator iterator, Container const&... containers) {
    using std::begin;
    using std::end;
    (iterator = std::copy(begin(containers), end(containers), iterator), ...);
}

Upvotes: 12

Related Questions