Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

gcc

c++17

clang

cl

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.

like image 769
Timo Avatar asked Apr 03 '18 16:04

Timo


People also ask

How to resolve “no matching function for call to C++”?

When we are calling some function but there is not matching function definition argument, then we get compilation error as No matching function for call to c++. To resolve this error, We need to pass appropriate matching argument during function call.

What is wrong with MSVC lambda expressions?

This is a problem with MSVC. clang and g++ are correct. A lambda-expression whose smallest enclosing scope is a block scope (6.3.3) is a local lambda expression; any other lambda-expression shall not have a capture-default or simple-capture in its lambda-introducer.

What does it mean when a function has no matching error?

Sometimes it also give that type mismatch or can not convert from one data type to another. You will get error for no matching function call when generally you are passing object / pointer / reference in function call and function definition is not able to match and accept that argument.

Why do I get a type mismatch error when calling a function?

If function call and function definition arguments are not matching then you might get this error. Depend on compiler to compiler you might get different errors. Sometimes it also give that type mismatch or can not convert from one data type to another.


1 Answers

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), ...);
}
like image 191
Barry Avatar answered Nov 07 '22 19:11

Barry