Clang和GCC在解析可变参数函数模板重载时的不同行为

时间:2016-07-15 08:48:11

标签: 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>
    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.2gcc 6.1合并得很好,但我尝试过的每个clang版本都会拒绝,甚至clang 3.8。 clang抱怨说这个电话很模糊。 (如果有人可以尝试使用VS编译它,我会很感激,因为我现在无法访问它。)

哪种编译器是正确的,我该如何解决这种差异呢?

更新虽然我仍然不确定哪种编译器的行为符合标准(更多),但我找到了一种解决方法:专业化{{1指向函数的指针,并避免盲目使用可变参数fn<T>。 (好吧,我们仍然没有指出成员函数的指示......现在我要忽略它们。:/)Example

operator()

3 个答案:

答案 0 :(得分:2)

我认为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()版本。

答案 1 :(得分:2)

这是一个GCC错误。请注意,即使使用类型为fn<II>的参数调用,GCC也始终调用char版本。编译器无法确定要调用哪个函数模板,因为它们具有完全相同的签名,而GCC只是随意选择一个。

答案 2 :(得分:0)

如果charint是独立类型而没有隐式转换,则代码可以正常运行。但是,由于charint可以在彼此之间隐式转换(是的,intchar可以隐式转换!),因此调用中可能存在歧义。如果存在,GCC在选择不需要转换的呼叫时会做出直观的例外。

编辑:我已经指出有一个模板化的参数在这里获得自己的operator()函数。这是我绝对没有看到的。