8

Consider the following code:

// Preamble
#include <iostream>
#include <type_traits>

// A base class
template <class T>
struct base {void operator()(T){};};

// Two derived classes inheriting from the same base classes
template <class... T>
struct derived1: base<T>... {using base<T>::operator()...;};
template <class... T>
struct derived2: base<T>... {using base<T>::operator()...;};

// A class inheriting from both derived1 and derived2
template <class T0, class... T>
struct functor: derived1<T0>, derived2<T0, T...> {
    using derived1<T0>::operator();
    using derived2<T0, T...>::operator();
};

// Main function
int main() {
    std::cout << std::is_invocable_v<functor<int, float, char>, int> << "\n";
    std::cout << std::is_invocable_v<functor<int, float, char>, float> << "\n";
    std::cout << std::is_invocable_v<functor<int, float, char>, char> << "\n";
    return 0;
}

The call to functor<int, float, char>::operator()(int) is ambiguous because this operator is inherited twice, from both derived1 and derived2 (and let's say that for convoluted SFINAE purpose I want it to be ambiguous).

On clang++-5.0, the output of the code is 0, 1, 1, while on g++-7.2 the output is 1, 1, 1. Which one is right? And would there be a workaround, creating a new struct is_unambiguously_invocable while waiting for the bugfix?

Vincent
  • 57,703
  • 61
  • 205
  • 388
  • @Barry - interesting; another way to reproduce the bug pre-C++17: tring to implement a custom `isInvocableF()` as follows: `template constexpr std::false_type isInvocableF (...) { return {}; } template constexpr auto isInvocableF (int) -> decltype( std::declval()(std::declval()...), std::true_type{} ) { return {}; }`. Following your example, calling `isInvocableF(0)` you get 1 (`std::true_type`) from g++, 0 (`std::false_type`) from clang++. – max66 Mar 03 '18 at 14:45
  • 1
    @Barry Don't answer in comments; you are leaving questions in the unanswered C++17 queue! – Yakk - Adam Nevraumont Aug 29 '18 at 18:31
  • @Yakk-AdamNevraumont As you wish. – Barry Aug 29 '18 at 18:49

1 Answers1

2

Your reasoning is correct. Note that gcc correctly disallows the call itself:

functor<int, float, char>()(42); // error: base<int> is an ambiguous base

It just incorrectly detects that this invocation is ill-formed. Reported this as gcc bug 84869. T.C. added a further reduced reproduction in the bug report that does not have library dependencies:

struct base {
    void operator()(int ) { }
};

struct a : base { };
struct b : base { };

struct f: a, b {
    using a::operator();
    using b::operator();
};

template<class T> auto g(int) -> decltype(T()(0), 0);
template<class T> auto g(...) -> long;

template<class, class> struct Same;
template<class T> struct Same<T, T> {};

Same<decltype(g<f>(0)), long> s; // should be okay, but gcc errors because it
                                 // thinks decltype(g<f>(0)) is int
Barry
  • 286,269
  • 29
  • 621
  • 977