c++copy-constructorc++17move-constructorimplicit-declaration

Unexpectedly missing implicitly declared copy/move constructors


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?


Solution

  • 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.