Учтите следующее:
#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 не может его скомпилировать (глубоко внутри std::is_invocable_r
):
error: invalid use of incomplete type
‘struct std::__or_<std::is_void<C>, std::is_convertible<C, C> >’
как делает Clang 5.0.1:
error: no type named 'type' in
'std::__or_<std::is_void<C>, std::is_convertible<C, C> >'
Из этого я заключаю, что C
отсутствует перемещение и копирование конструкторов. Действительно, если мы раскомментируем его конструктор перемещения (1), этот код компилируется.
Я считаю, что требования к ним, которые должны быть неявно заявлены, выполнены. Почему нет?
Я думаю, что здесь:
F<C()> f_;
C
является неполным типом. В F
, C
заменяет параметр шаблона R
, который затем используется в качестве аргумента шаблона std::is_invocable_r_v
, Стандарт не позволяет использовать неполные типы в качестве аргументов шаблона std::is_invocable_r_v
и это приводит к неопределенному поведению. Неопределенное поведение включает, среди прочего, произвольное поведение компилятора во время компиляции.
Обратите внимание, что я не совсем уверен в своем ответе, главным образом потому, что ни F::F
конструктор, ни его шаблон operator()
создается экземпляр.
Других решений пока нет …