Даже при «гарантированном разрешении копирования» (которое, к сожалению, немного ошибочно), стандарт C ++ требует [dcl.fct] / 11 , что возвращаемый тип функции
[…] не должно быть неполным (возможно, cv-квалифицированным) типом класса в контексте определения функции, если только функция не удалена.
Использование типа заполнителя для Тип возвращаемого значения в определении вашей функции (как было также предложено Максом Лангофом в комментариях) должен обойти проблему в этом случае:
template <typename Derived>
struct Interface
{
auto f() const { return static_cast<const Derived&>(*this).f_impl(); }
};
рабочий пример здесь
Обратите внимание, что «гарантированное разрешение на копирование» на самом деле не является гарантией того, что копия будет удалена, так же как и изменение языка, означающее, что копия никогда не делается в первую очередь. Без наличия копии, исключать также нечего ...
Не делать копию означает, что компилятор должен создать объект непосредственно в месте назначения возвращаемого значения. И для этого требуется полный тип ...