Clang 和 GCC 在解决可变参数函数模板重载时的行为不同
Clang and GCC different behavior when resolving variadic function template overload
请考虑以下代码:
#include <utility>
int foo_i(int x) { return x + 1; }
char foo_c(char x) { return x + 1; }
using II = int (*)(int);
using CC = char (*)(char);
template<typename F>
struct fn {
F f;
template<typename... Args>
decltype(auto) operator()(Args&&... args) const
{
return f(std::forward<Args>(args)...);
}
};
struct fn_2 : private fn<II>, private fn<CC> {
fn_2(II fp1, CC fp2)
: fn<II>{fp1}
, fn<CC>{fp2}
{}
using fn<II>::operator();
using fn<CC>::operator();
};
int main()
{
fn_2 f(foo_i, foo_c);
f(42);
}
基本上,fn<T>
存储一个类型为 T
的函子(不一定是函数指针),它的可变参数operator()
将所有内容转发给函子。
这段代码在 gcc 4.9.2 到 gcc 6.1 中编译得很好,但被我尝试过的每个 clang 版本都拒绝了,甚至是 clang 3.8。(如果有人可以尝试使用 VS 编译它,我将不胜感激,因为我现在无法访问它。
哪个编译器是正确的,我该如何解决这种差异?
更新:虽然我仍然不确定哪个编译器的行为(更)符合标准,但我找到了一个解决方法:专门fn<T>
指向函数的指针,并避免盲目使用可变参数operator()
的需要。(好吧,我们仍然省略了指向成员函数的指针...现在我要忽略它们。:/) 示例:
template<typename F>
struct fn : private F {
using F::operator();
};
template<typename R, typename... Args>
struct fn<R (*)(Args...)> {
fn(R (*f)(Args...)) noexcept : f_(f) {}
R operator()(Args&&... args) const
{
return f_(std::forward<Args>(args)...);
}
private:
R (*f_)(Args...);
};
我认为clang
在这里不编译代码,因为operator()
显然是模棱两可的。如果您考虑一下,从提供的模板签名中operator()
不清楚应该首选哪个功能。您必须根据您在 fn
中存储的函数向编译器提供其他提示。
这是我的解决方案:
#include <utility>
#include <type_traits>
#include <iostream>
int foo(int x) { return x + 1; }
char foo(char x) { return x + 1; }
using II = int (*)(int);
using CC = char (*)(char);
template <bool... B>
struct bool_pack {};
template <bool... V>
using all_true = std::is_same<bool_pack<true, V...>, bool_pack<V..., true>>;
template <typename... Args> struct packed {};
template <typename T> struct func_traits;
template <typename R, typename... Args>
struct func_traits<R(*)(Args...)> {
using type = packed<Args...>;
};
template<typename F>
struct fn {
F f;
template<typename... Args,
typename std::enable_if<std::is_same<packed<Args...>, typename func_traits<F>::type>::value>::type* = nullptr>
auto operator()(Args&&... args) const
{
return f(std::forward<Args>(args)...);
}
};
struct fn_2 : private fn<II>, private fn<CC> {
fn_2(II fp1, CC fp2)
: fn<II>{fp1}
, fn<CC>{fp2}
{}
using fn<II>::operator();
using fn<CC>::operator();
};
int main()
{
fn_2 f(static_cast<II>(foo),
static_cast<CC>(foo));
std::cout << f(42) << std::endl;
std::cout << f('a') << std::endl;
}
没什么好看的,但我正在使用enable_if
来帮助编译器根据存储函数的 arity 类型选择正确的operator()
版本。
GCC错误。请注意,GCC 始终调用fn<II>
版本,即使使用类型 char
的参数调用也是如此。编译器无法判断要调用哪个函数模板,因为它们具有完全相同的签名,而 GCC 只是任意选择一个。
如果 char
和 int
是没有隐式转换的独立类型,则代码将正常工作。但是,由于char
和int
可以在彼此之间隐式转换(是的,int
to char
可以隐式转换!),因此调用中可能存在歧义。GCC 在选择根本不需要转换的调用(如果存在)时执行直观的例外。
编辑:有人指出,有一个模板化的参数,它在这里有自己的operator()函数。这是我绝对没有看到的。
- 将重载的成员函数传递给函数模板
- 使用模板重载函数
- 函数模板实例化、替换和重载解析的顺序是什么?
- 基于 SFINAE 的特征实现问题与函数模板重载
- 如何通过签名作为模板参数来解决重载函数?
- 将模板(没有规范)传递给 std::thread() 会出现错误:<未解析的重载函数类型>匹配错误
- 重载模板<类型名...>类的函数模板
- 使用类指针重载C++命名空间函数模板专用化替代方法?
- 按返回类型重载函数模板
- 定义重载C++函数模板的原型时,使用其名称引用以前的定义是否合法?
- 用另一个函数模板重载函数模板合法吗
- 获取重载函数模板的地址
- 部分专用结构与重载函数模板
- 基于参数函数参数类型重载函数模板
- 重载函数模板
- 基于 C++98 中函数对象运算符 () 签名的“重载”函数模板
- 具有引用参数的重载函数模板
- 用子类调用时匹配模板而不是基类的重载函数模板
- 有时可以使用重载函数模板的地址
- 在命名空间中重载函数模板