Clang 和 GCC 对于重载函数模板是否不明确存在分歧

Bul*_*net 23 c++ language-lawyer c++11

我正在尝试移植一些为 GCC (8.2) 编写的代码以供 Clang 编译:

#include <tuple>

struct Q{};

using TUP = std::tuple<Q>;


template<typename Fn>
inline
void feh(Fn&, const std::tuple<>*)
{}

template<typename Fn, typename H>
inline
void feh(Fn& fn, const std::tuple<H>*)
{
    fn(H{});
}

template<typename  Fn, typename H, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R...>*)
{
    fn(H{});
    using Rest = const std::tuple<R...>*;
    feh<Fn, R...>(fn, static_cast<Rest>(nullptr));
}

template<typename Tuple, typename Fn>
inline
void fe(Fn& fn, const Tuple  *  tpl =  nullptr)
{
    feh(fn, tpl);
}

int main()
{
    auto r = [] (Q const&) {};
    TUP tup;
    fe<TUP>(r, &tup);
}
Run Code Online (Sandbox Code Playgroud)

GCC 8.2(和 12.1)可以很好地编译代码。fe然而,Clang 11.0.0(和 14.0.0)抱怨to的调用在和feh之间不明确。void feh(Fn& fn, const std::tuple<H>*) [with Fn = (lambda at <source>:38:14), H = Q]void feh(Fn& fn, const std::tuple<H, R...>*) [with Fn = (lambda at <source>:38:14), H = Q, R = <>]

https://godbolt.org/z/5E9M6a5c6

哪个编译器是正确的?

我怎样才能编写这段代码以便两个编译器都接受它?

和 折叠表达式都if constexpr可以在 C++17 中使用,但这是许多项目包含的库头,并且并非所有项目都是使用 C++17 编译的。我需要一个适用于 C++11 的解决方案。

use*_*570 17

哪个编译器是正确的?

Clang 拒绝代码是错误的,因为第一个重载候选者feh(Fn& fn, const std::tuple<H>*)应该优先于另一个候选者feh(Fn& fn, const std::tuple<H, R...>*),因为前者比后者更专业。

换句话说,没有包的版本被认为更专业,因此如果它与调用匹配,则应该是首选。


这是因为,基本上(大致)一个函数模板被认为比另一个函数模板更专业,后者应该能够接受前者可以接受的所有模板参数,但反之则不然。

现在,在您给定的示例中,重载feh(Fn& fn, const std::tuple<H, R...>*)可以接受(或使用)前者feh(Fn& fn, const std::tuple<H>*)可以接受的所有模板参数,但反之则不然。因此,前者比后者更专业。有关此过程的更多技术细节,请参阅模板推导中的部分排序过程是什么或来自[temp.deduct.partial]/10,其中指出:

如果对于用于确定排序的每对类型,F 中的类型至少与 G 中的类型一样专业,则函数模板 F 至少与函数模板 G一样专业F。as且至少不像 那样专业化GFGGF

(强调我的)


Ted*_*gmo 11

clang++是正确的,因为两个函数匹配得同样好。我不确定哪个编译器是正确的,但是......

C++11 解决方案可能只是添加该Rest部分必须至少包含一种类型的要求,并且只需添加R1. 这意味着其余代码可以保持不变:

template<typename Fn, typename H, typename R1, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R1, R...>*)
{
    fn(H{});
    using Rest = const std::tuple<R1, R...>*;
    feh<Fn, R1, R...>(fn, static_cast<Rest>(nullptr));
}
Run Code Online (Sandbox Code Playgroud)

C++17 解决方案是删除其他feh重载并使用折叠表达式

template <typename Fn, typename... H>
inline void feh(Fn& fn, const std::tuple<H...>*) {
    (..., fn(H{}));
}
Run Code Online (Sandbox Code Playgroud)

这是逗号运算符上的一元左折叠,“展开”后变为:

(((fn(H1{}), fn(H2{})), ...), fn(Hn{}))
Run Code Online (Sandbox Code Playgroud)


MSa*_*ers 6

到目前为止,最简单的解决方案是if constexpr

template<typename  Fn, typename H, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R...>*)
{
    fn(H{});
    if constexpr (sizeof...(R) > 0) {
        using Rest = const std::tuple<R...>*;
        feh<Fn, R...>(fn, static_cast<Rest>(nullptr));
    }
}
Run Code Online (Sandbox Code Playgroud)

并删除有问题的过载。

  • *“到目前为止,最简单的解决方案是 `if constexpr`”* 我发现折叠表达式更简单:`(fn(R{}), ...);` (3认同)