gcc 和 clang 抛出“没有匹配的函数调用”但 msvc (cl) 编译并按预期工作

Posted

技术标签:

【中文标题】gcc 和 clang 抛出“没有匹配的函数调用”但 msvc (cl) 编译并按预期工作【英文标题】:gcc and clang throw "no matching function call" but msvc (cl) compiles and works as expected 【发布时间】:2018-04-03 16:10:19 【问题描述】:

我写了一个小函数模板,可以在一个新容器中加入不同的容器:

#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;

将 MSVC (cl.exe) 与 /std:c++17 一起使用,代码可以编译并且运行良好。但是使用 clang-6.0 或 gcc-7.3 编译时,会抛出编译器错误。 IE。 gcc 说

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

显然没有定义具有此签名的函数。但我不明白为什么它会尝试调用这样的函数。不应该这样解决吗

// 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);

为什么 gcc 会尝试实例化 join(std::insert_iterator&lt;std::unordered_set&lt;std::__cxx11::basic_string&lt;char&gt;&gt;&gt;&amp;)

Here 是使用编译器资源管理器的示例。

【问题讨论】:

对它们重新排序的另一个好处是,您可以避免重复逻辑(即 for 循环)。 这是一个非常危险的实现,与 strcat 等优秀的旧 C 函数有同样的问题!什么,如果输出迭代器到达目标容器的结束迭代器??? @Aconcagua - 输出迭代器是一个插入器。我想这两个函数位于 impl 命名空间中,这就是为什么它们“一般”很危险。 @Aconcagua 好吧,impl 命名空间中的函数旨在仅从全局范围函数 join 中使用,该函数使用 insert_iterator,这意味着它每次分配都会插入到容器中。 OK...我个人仍然会在模板参数中反映这一点。也许我有点安全***?旁注:短一点:auto it = std::back_inserter(container);... 【参考方案1】:

gcc 和 clang 是正确的。 MSVC 仍然存在正确的模板名称查找问题(即“两阶段查找”)。

join,在join(iterator, containers...) 中,是一个从属名称。找到该名称的候选者是:

模板定义处的所有名称。这个查找只是找到它自己(可变参数重载),而不是另一个重载(2-arg 重载),因为它还没有被声明。 ADL 可以在其参数上找到的所有名称。这些参数都没有 impl 作为关联的命名空间,因此也找不到其他重载。

在这种情况下,修复很简单:只需重新排序两个 join() 重载。这确保了 2-arg join() 将被第一个项目符号点找到。


请注意,在 C++17 中,您甚至不需要两个重载。一个就可以了:

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...);
    


还可以考虑使用std::copy() 而不是循环。这实际上允许:

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), ...);

【讨论】:

如果您使用 /permissive- 标志(位于项目设置的 c/c++->语言节点,标记为“一致性模式”),MSVC 会表现出正确的行为

以上是关于gcc 和 clang 抛出“没有匹配的函数调用”但 msvc (cl) 编译并按预期工作的主要内容,如果未能解决你的问题,请参考以下文章

为啥 clang 和 gcc 在这个虚拟继承代码上存在分歧?

GCC 和 clang 上的 MSVC /Zp 替代方案是啥?

C++学习(三四八)CLang GCC

Clang 和 GCC 接受有问题的 sizeof

你如何为 clang 和 gcc 编写一个 makefile?

linux下Clang和gcc的区别