Consider the following:
#include <type_traits>
template <typename>
struct F;
template <typename R, typename... As>
struct F<R(As...)>
{
template <typename F_, std::enable_if_t<
std::is_invocable_r_v<R, std::decay_t<F_>, As...>>*...>
F(F_&&) {}
F() = default;
template <typename... As_, std::enable_if_t<
std::is_invocable_v<void(As&&...), As_...>>*...>
R operator()(As_&&...)
{ return R(); }
};
struct C
{
F<C()> f_;
// C(C&&) = default; // <<< 1
};
int main() {
F<C(int)> x;
auto y = x;
}
gcc 7.3.0 fails to compile it (deep within std::is_invocable_r
):
error: invalid use of incomplete type
‘struct std::__or_<std::is_void<C>, std::is_convertible<C, C> >’
as does clang 5.0.1:
error: no type named 'type' in
'std::__or_<std::is_void<C>, std::is_convertible<C, C> >'
From this I deduce that C
is missing move and copy constructors. Indeed, if we uncomment its move constructor (1), this code compiles.
I believe the requirements for them to be implicitly declared are satisfied. Why aren't they?
My best guess is that here:
F<C()> f_;
C
is an incomplete type. Within F
, C
substitutes the template parameter R
, which is then used as a template argument of std::is_invocable_r_v
. The Standard does not allow to use incomplete types as template arguments of std::is_invocable_r_v
and it results in undefined behavior. Undefined behavior includes, among others, arbitrary behavior of a compiler during compilation.
Note that I am not completely sure about my answer, mainly because neither the templated F::F
constructor nor its templated operator()
is instantiated.