Reputation: 9835
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
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:
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