4

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...);
};
19
  • Well, there are two operator() functions that have different signatures. The two are overloads. The ambiguity comes from the possibility of implictly converting char to int. Commented Jul 15, 2016 at 8:51
  • @PaulStelian Then is it wrong for gcc to accept it? Commented Jul 15, 2016 at 8:52
  • I don't think it's wrong per se. But I don't think it's strictly standard compliant either (try the -ansi parameter, this should disable gcc extensions) Commented Jul 15, 2016 at 8:55
  • 2
    @PaulStelian FYI, GCC manual says that -ansi in C mode is equivalent to -std=c90 and in C++ mode is equivalent to -std=c++98. Simple -std=c++14 is enough. Commented Jul 15, 2016 at 9:02
  • 1
    Note that with trailing return type syntax, both show an ambiguous call Demo Commented Jul 15, 2016 at 9:07

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

14 Comments

But you have to give exact same type of argument (so lvalue cannot be used for rvalue).
Note also that you wrongly use std::enable_if, it should not be as default template argument. Use std::enable_if_t<...>* = nullptr instead.
There are two problems left: (1) You specialized for func_traits<R(*)(Args...)>, but in my example the T in fn<T> is not necessarily a function pointer. It could be any functor type. (2) Exactly as @Jarod42 said. Not only references: I can't pass a say a long to a fn_2 anymore.
@ZizhengTai Yes, I did know that would be the requirement but I just wanted to show an example of how it should be made to work. You will ofcourse have to make use of more generic version func_traits and make use of more type traits like std::is_convertible and comparing types only after remove_const and remove_reference. All that I guess is just more bolier plate code
@PaulStelian What if OP decides to add one more function taking long later ?
|
2

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.

2 Comments

Just a note to say that Clang's error message looks strange in this case - it seems to abruptly stop in the middle of a note:. Something similar happens to MSVC, funnily enough. Only EDG gave a nice and complete error message; it's quoted in the comments to the question.
@bogdan This is working as intended: we suppress the code snippet on the second note because its location is exactly the same as the prior note, and there's no new information to show in it.
0

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.

2 Comments

No, it wouldn't be fine, because the overload resolution takes place, before the arguments are forwarded to the actual functions and both base classes have a templated operator() that just takes ANY argument. (just check with e.g. std::string).
Oh did not spot that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.