简体   繁体   中英

Clang and GCC different behavior when resolving variadic function template overload

Consider this code:

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

Basically, fn<T> stores a functor (not necessarily a function pointer) of type T , and its variadic operator() forwards everything to the functor.

This code compiles fine with gcc 4.9.2 through gcc 6.1 , but is rejected by every clang version I've tried, even clang 3.8 . clang complains that the call is ambiguous. (I'd appreciate it if someone can try compile it with VS, because I don't have access to it right now.)

Which compiler is right, and how can I work around this discrepancy?

UPDATE: Although I'm still not sure which compiler's behavior is (more) compliant to the standard, I've found a workaround: Specializing fn<T> on pointers to functions, and avoid the need to blindly use variadic operator() . (Well, we've still left out pointers to member functions... For now I'm going to ignore them. :/) Example :

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

I think clang is right here to not compile the code as the operator() is clearly ambiguous. If you think about it, from the provided template signature for operator() it's not clear which function should be preferred. You will have to provide additional hints to the compiler based on your stored function in fn .

Here is my solution:

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

Nothing fancy, but I am using enable_if to help compiler choose the correct version of operator() based upon the arity types of the stored function.

This is a GCC bug. Note that GCC always calls the fn<II> version, even if called with a parameter of type char . There is no way a compiler can tell which function template to call, because they have the exact same signature, and GCC is just picking one arbitrarily.

The code would work perfectly fine if char and int were independent types with no implicit conversions. However, since char and int can be implicitly converted between each other (yes, int to char can convert implicitly!), there may be an ambiguity in the call. GCC does the intuitive exception in selecting the call requiring no conversion at all, if present.

EDIT: I've been pointed out there is a templated argument that gets its own operator() function in here. This is something I definitely did not see.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM