Describe the bug
std::invoke_result fails to compile if it's used within sfinae context and the second template argument is an abstract base class.
See the minimal example below for more info, it's really hard to describe in words...
Command-line test case
C:\Users\raffael\temp\c++scratch\CMakeProject1\CMakeProject1>type CMakeProject1.cpp
#include <type_traits>
class Abstract {
public:
virtual void Foo() = 0;
};
template<class T>
class WithMember {
T t_;
};
class Functor {
public:
template<typename Indices>
WithMember<Indices>
operator()(const Indices& indices) {
return WithMember<Indices>();
}
};
template <class T,
typename =
std::invoke_result_t<T, Abstract>>
void foo(int) {}
int main()
{
foo<Functor>(0);
}
C:\Users\raffael\temp\c++scratch\CMakeProject1\CMakeProject1>cl /EHsc /W4 /WX .\CMakeProject1.cpp /std:c++17
Microsoft (R) C/C++ Optimizing Compiler Version 19.27.29111 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
CMakeProject1.cpp
.\CMakeProject1.cpp(10): error C2259: 'Abstract': cannot instantiate abstract class
.\CMakeProject1.cpp(3): note: see declaration of 'Abstract'
.\CMakeProject1.cpp(10): note: due to following members:
.\CMakeProject1.cpp(10): note: 'void Abstract::Foo(void)': is abstract
.\CMakeProject1.cpp(5): note: see declaration of 'Abstract::Foo'
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.27.29110\include\type_traits(1503): note: see reference to class template instantiation 'WithMember<Abstract>' being compiled
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.27.29110\include\type_traits(1502): note: while compiling class template member function 'WithMember<Abstract> std::_Invoker_functor::_Call<T,Abstract>(_Callable &&,Abstract &&) noexcept(<expr>)'
with
[
T=Functor,
_Callable=Functor
]
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.27.29110\include\type_traits(1748): note: see reference to alias template instantiation 'std::_Decltype_invoke_nonzero<Functor,Abstract,>' being compiled
.\CMakeProject1.cpp(24): note: see reference to alias template instantiation 'std::invoke_result_t<Functor,Abstract>' being compiled
Expected behavior
The code should successfully compile. Clang and GCC don't have a problem with it. Also it works, if we replace std::invoke_result_t<T, Abstract> with decltype(std::declval<T>()(std::declval<Abstract>()))
STL version
Microsoft Visual Studio 2019
Version 16.7.2
Thanks, this looks like a bug. It also repros with Clang 10 compiling our STL, which rules out an MSVC compiler bug. Clang's error message indicates that our centralized noexcept machinery might be involved - although you aren't asking is_nothrow_invocable here, the compiler is evaluating some noexcept markings and that seems to force WithMember<Abstract> to be instantiated:
C:\Temp>clang-cl /EHsc /nologo /W4 /std:c++17 abstract.cpp
abstract.cpp(10,5): error: field type 'Abstract' is an abstract class
T t_;
^
S:\GitHub\STL\out\build\x86\out\inc\type_traits(1491,18): note: in instantiation of template class
'WithMember<Abstract>' requested here
noexcept(static_cast<_Callable&&>(_Obj)(static_cast<_Types&&>(_Args)...)))
^
S:\GitHub\STL\out\build\x86\out\inc\type_traits(1700,33): note: in instantiation of exception specification for
'_Call<Functor, Abstract>' requested here
_Invoker1<_Callable, _Ty1>::_Call(_STD declval<_Callable>(), _STD declval<_Ty1>(), _STD declval<_Types2>()...));
^
S:\GitHub\STL\out\build\x86\out\inc\type_traits(1703,38): note: in instantiation of template type alias
'_Decltype_invoke_nonzero' requested here
struct _Invoke_traits_nonzero<void_t<_Decltype_invoke_nonzero<_Callable, _Ty1, _Types2...>>, _Callable, _Ty1,
^
S:\GitHub\STL\out\build\x86\out\inc\type_traits(1763,1): note: during template argument deduction for class template
partial specialization '_Invoke_traits_nonzero<void_t<_Decltype_invoke_nonzero<_Callable, _Ty1, _Types2...> >,
_Callable, _Ty1, _Types2...>' [with _Callable = Functor, _Ty1 = Abstract, _Types2 = <>]
using invoke_result_t = typename _Select_invoke_traits<_Callable, _Args...>::type;
^
S:\GitHub\STL\out\build\x86\out\inc\type_traits(1763,1): note: in instantiation of template class
'std::_Invoke_traits_nonzero<void, Functor, Abstract>' requested here
abstract.cpp(24,8): note: in instantiation of template type alias 'invoke_result_t' requested here
std::invoke_result_t<T, Abstract>>
^
abstract.cpp(25,8): note: in instantiation of default argument for 'foo<Functor>' required here
void foo(int) {}
^~~~~~~~
abstract.cpp(29,3): note: while substituting deduced template arguments into function template 'foo'
[with T = Functor, $1 = (no value)]
foo<Functor>(0);
^
abstract.cpp(5,16): note: unimplemented pure virtual method 'Foo' in 'Abstract'
virtual void Foo() = 0;
^
1 error generated.
Seems to be a direct consequence of the fact that we implement invoke_result_t in terms of decltype(std::invoke(...stuff...)) where std::invoke necessarily has conditional noexcept. noexcept(some_function()) must determine if calling some_function can throw, and if destroying any returned object can throw, which necessitates instantiating that object's class. This information isn't strictly necessary for invoke_result_t, which could be implemented via different machinery to avoid the conditional noexcept.
While we _could_ fix this, I'm not so sure we _should_. Programs that ask for invoke_result_t<F, Args..> generally do so because they're going to invoke(F, args...). Using common machinery means that the compile time for both is less than the sum of the compile times, which wouldn't be true if the machinery for each was distinct.
I don't see a bug. invoke_result is specified to have a member type only when "the expression INVOKE(declval<Fn>(), declval<ArgTypes>()...) is well-formed when treated as an unevaluated operand". This condition does not apply the decltype extra-special treatment in [dcl.type.decltype]/2 and therefore requires completion of the type.
While we _could_ fix this, I'm not so sure we _should_. Programs that ask for
invoke_result_t<F, Args..>generally do so because they're going toinvoke(F, args...). Using common machinery means that the compile time for both is less than the sum of the compile times, which wouldn't be true if the machinery for each was distinct.
In my case I used invoke_result to check the return type of an invocation and then check if this return type satisfied some other criteria. Based on that I then enabled/disabled some methods via SFINAE. I think think this is not such an uncommon usage.
Also I thought that invoke_result replaced result_of because, among some other shortcomings, result_of didn't work for abstract class types. See e.g. cpp-reference notes or P0604r0 which was merged into C++17. (Disclaimer: I don't understand the standard completly)
According to my understanding, the issue with result_of was because it used function types in its interface (in a strange way - stranger than std::function). result_of<Func(Args)> asks "what is the result of calling Func with Args". The function type even looks like a "call". However, the function type is actually "function taking Args and returning Func" which is super weird. (Note that std::function<Ret(Args)> doesn't have this weird distortion of meaning.)
This was a problem for asking result_of<Abstract(Args)> since the function type looks like it's returning Abstract by value (forbidden!) even though you just want to ask "what if I had an rvalue of type Abstract" which is reasonable (it could be static_cast<Abstract&&>(DerivedObject), for example). invoke_result<Abstract, Args> isn't vulnerable to this problem, and will return the correct answer.
Your scenario is different because you're not just asking what the result of invoking an Abstract rvalue would be - the templates involved are actually trying to instantiate an Abstract. As @timsong-cpp found, this indeed appears to be a by-design limitation of the current specification.
I'm going to resolve this as wontfix - if you believe that the Standard should support this usage, I encourage you to file a Library Working Group issue.
Okay, thanks for the consideration