https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71117
Bug ID: 71117 Summary: [6.1 regression] Overeager application of conversion to function pointer during overload resolution of call to function object Product: gcc Version: 6.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: eric.niebler at gmail dot com Target Milestone: --- Casey Carter recently posted this to the committee core- mailing list: <<<<<<<<<<< BEGIN It is a commonplace library technique to compose function objects, which are often empty, by inheritance to take advantage of the Empty Base Optimization. E.g., this program patterned on usage in range-v3 turns a function that accepts objects into a function that accepts iterators that denote objects: template <class T> T&& declval() noexcept; template <class, class> constexpr bool is_same = false; template <class T> constexpr bool is_same<T, T> = true; template <class F> struct indirected : F { indirected(F f) : F(f) {} template <class I> auto operator()(I i) -> decltype(declval<F&>()(*i)) { return static_cast<F&>(*this)(*i); } }; int main() { auto f = [](auto rng) { static_assert(is_same<decltype(rng), int>, ""); return 42; }; indirected<decltype(f)> i(f); static_assert(is_same<decltype(i(declval<int*>())), int>, ""); } Unfortunately, when the adapted function object is an underconstrained captureless generic lambda - as is the case in the example - composition by inheritance is extremely fragile. Since the lambda is captureless, its closure type has a member conversion operator that converts to a function pointer type with deduced return type. An attempt to call the derived type's call operator results in overload resolution instantiating the conversion operator's declaration necessitating return type deduction from the base object's call operator. When the base object's call operator is ill-formed for the particular argument types - again as is the case in the example - the program is ill-formed. GCC 6 and trunk tell me this program is ill-formed: casey@Semiregular:~/bugs$ ~/gcc/bin/g++ -std=c++14 repro.cpp repro.cpp: In instantiation of ‘main()::<lambda(auto:1)> [with auto:1 = int*]’: repro.cpp:17:25: required by substitution of ‘template<class auto:1> main()::<lambda(auto:1)>::operator decltype (((main()::<lambda(auto:1)>)0u).operator()(static_cast<auto:1>(<anonymous>))) (*)(auto:1)() const [with auto:1 = int*]’ repro.cpp:22:53: required from here repro.cpp:18:9: error: static assertion failed static_assert(is_same<decltype(rng), int>, ""); ^~~~~~~~~~~~~ older versions of GCC and all versions of Clang I've tried compile the program without diagnostics. Since hiding of conversion operators is based on the target types, and determining the target type of the problematic conversion operator results in the aforementioned ill-formed return type deduction, there seems to be no way to hide the problematic conversion operator. >>>>>>>>>>>>>>>>>>>>>>>> END To which Richard Smith replied: <<<<<<<<<<<<<<<<<<<<<<<< BEGIN template<typename T> using id = T; struct F { template<typename T> operator id<T(*)(T)>(); } f; int n = f(0); GCC accepts this and calls the conversion function template with T=int. Clang, EDG, MSVC reject. Per [over.call.object]/2, I think GCC is wrong per the current language wording. A conversion function template does not qualify as a "non-explicit conversion function declared in [F or base class thereof]" (because it is not a conversion function), and nor does a specialization of one (because it is not itself declared in F). I also don't see any way the current wording would take us to [temp.deduct.conv] for this case, nor how that wording would apply (since we don't have a function type that's required as the result of the conversion). Perhaps GCC is following the rules of [temp.deduct.call] in this case, treating the (pointee type of the) result type of the conversion function as if it were the function type of the callee? In the abstract, GCC's approach to this situation seems superior -- it's able to use a conversion to function pointer in many cases where other compilers can't -- but I'm a little hesitant to suggest we adopt that approach since it breaks examples like yours. >>>>>>>>>>>>>>>>>>>>>>>>> END