C++ Clang和GCC在解决变量函数模板重载时的不同行为

C++ Clang和GCC在解决变量函数模板重载时的不同行为,c++,gcc,clang,c++14,language-lawyer,C++,Gcc,Clang,C++14,Language Lawyer,考虑以下代码: #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>

考虑以下代码:

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

如果
char
int
是没有隐式转换的独立类型,那么该代码将工作得非常好。但是,由于
char
int
可以相互隐式转换(是的,
int
char
可以隐式转换!),因此调用中可能存在歧义。GCC在选择完全不需要转换的调用(如果存在)时会出现直观的异常


编辑:有人指出,这里有一个模板参数,它有自己的操作符()函数。这是我绝对没有看到的。

我认为
clang
就是在这里不编译代码的,因为
操作符()
显然是不明确的。如果您仔细想想,从为
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;
}
#包括
#包括
#包括
intfoo(intx){返回x+1;}
char foo(char x){return x+1;}
使用II=int(*)(int);
使用CC=char(*)(char);
模板
结构bool_包{};
模板
使用all_true=std::is_same;
模板结构压缩{};
模板结构函数特性;
模板
结构函数特性{
使用类型=包装;
};
模板
结构fn{
F;
模板
自动运算符()(Args&&…Args)常量
{
返回f(标准::转发(参数)…);
}
};
结构fn_2:私有fn,私有fn{
fn_2(II fp1,CC fp2)
:fn{fp1}
,fn{fp2}
{}
使用fn::operator();
使用fn::operator();
};
int main()
{
fn_2 f(静态铸造(foo),
静态_-cast(foo));

std::cout这是一个GCC错误。请注意,GCC总是调用
fn
版本,即使使用类型为
char
的参数调用也是如此。编译器无法判断要调用哪个函数模板,因为它们具有完全相同的签名,而GCC只是任意选择一个。

嗯,有两个运算符()具有不同签名的函数。这两个函数都是重载。歧义来自于将char隐式转换为int的可能性。@PaulStelian那么gcc接受它是错误的吗?我不认为它本身是错误的。但我也不认为它严格符合标准(尝试-ansi参数,这应该禁用gcc扩展)@ PaulSteliFi,GCC手册中的C代码中的代码> > ANSI 相当于<> > STD= C90,C++模式相当于<> > STD= C++ 98 。简单的<代码> -STD= C++ 14 已经足够了。注意,用后退返回类型语法,都显示出模糊的调用,但是必须给出完全相同的类型的参数。(因此左值不能用于右值)。还要注意,如果错误地使用了
std::enable_,则它不应作为默认模板参数。如果t*=nullptr
,则使用
std::enable_。还剩下两个问题:(1)您专门用于
func\u traits
,但在我的示例中,
fn
中的
T
不一定是函数指针。它可以是任何函数类型。(2)正如@Jarod42所说。不仅仅是引用:我不能再把say a
long
传递给a
fn_2
。@ZizhengTai是的,我知道这是一个要求,但我只是想展示一个它应该如何工作的例子。当然,你必须使用更通用的版本
func_traits
,并使用mo像
std::是可转换的
,只有在
remove\u const
remove\u reference
之后才能比较类型。我想这只是更为简单而已code@PaulStelian如果OP决定以后再添加一个函数来执行
long
,该怎么办?不,这不会好的,因为重载解决方案需要e、 在参数被转发到实际函数之前,两个基类都有一个模板化的
运算符()
,该运算符只接受任何参数。(只需使用例如
std::string
进行检查)哦,没有发现这一点。只是一个注释,说Clang的错误信息在这种情况下看起来很奇怪,它似乎突然在“代码>注释中:中间。类似的事情发生在MSVC上,非常可笑。只有EDG给出了一个完整而完整的错误信息;它在评论中引用了这个问题。”波格丹:这是我们的目的:我们支持。按第二个注释上的代码段,因为它的位置与前一个注释完全相同,并且没有显示新信息。
#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;
}