SFINAE отправка между недействительным и недействительным методом
У меня есть что-то вроде следующего:
template <typename T>
struct Base {
auto func() {
// do stuff
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
}
};
struct A : Base<A> {
int func_impl() {
return 0;
}
};
struct B : Base<B> {
void func_impl() {
}
};
int main() {
A a;
int i = a.func();
B b;
b.func();
return 0;
}
Проблема в том, что я не могу объявить тип возвращаемого значения func_impl
в производном классе для void
как показано в B
, Я попытался решить проблему с помощью SFINAE следующим образом:
template <typename T>
struct Base {
template <typename = enable_if_t<!is_void<decltype(declval<T>().func_impl())>::value>>
auto func() {
// do stuff
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
}
template <typename = enable_if_t<is_void<decltype(declval<T>().func_impl())>::value>>
void func() {
// do stuff
static_cast<T&>(*this).func_impl();
// do stuff
}
};
Но компилятор выдает ошибку: invalid use of incomplete type 'struct A'
а также invalid use of incomplete type 'struct B'
, Есть ли способ сделать то, что я хочу?
2 ответа
Попробуй с
template <typename T>
struct Base {
template <typename U = T, typename = enable_if_t<!is_void<decltype(declval<U>().func_impl())>::value>>
auto func() {
// do stuff
return static_cast<T&>(*this).func_impl();
}
template <typename U = T, typename = enable_if_t<is_void<decltype(declval<U>().func_impl())>::value>>
void func() {
// do stuff
static_cast<T&>(*this).func_impl();
}
};
Я имею в виду... SFINAE применяется над шаблонами; если вы хотите включить / отключить методы внутри класса, они должны быть шаблонными методами (тот факт, что класс / структура является шаблоном, класс / структура не учитываются: это метод, который должен быть шаблоном.
И СФИНА часть (std::enable_if_t
в этом случае) должны зависеть от шаблона метода (U
в моем примере).
Ps: во всяком случае, я не вижу проблем с возвратом void
Ситуации, подобные этой:
auto x = static_cast<T&>(*this).func_impl();
// do stuff
return x;
Призыв к обычному типу Void. Другими словами, так как вам не нужно x
здесь, вам просто нужно вернуть его, вам действительно нужно func()
возвращать void
? Я считаю, что это обычно не так. Любой старый пустой тип, который люди не должны использовать, достаточно хорош. Итак, давайте упростим запись этого случая без дублирования:
namespace details {
struct Void { }; // not intended to be used anywhere
}
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args) {
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
details::Void invoke_void(F&& f, Args&&... args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
return details::Void{};
}
Эта реализация использует функции библиотеки C++17, но может быть реализована в C++14. Это дает нам invoke()
это меняет void
за Void
, что позволяет просто написать:
auto func() {
// do stuff
auto x = invoke_void([](auto& x){ return x.func_impl(); },
static_cast<T&>(*this));
// do stuff
return x;
}
Это немного многословно, но по крайней мере нам не нужно дублировать func()
- только одна функция прекрасно справляется с обоими случаями.
Другая альтернатива, более простая или более сложная в зависимости от вашей интерпретации, состоит в том, чтобы переупорядочить тело func()
:
auto func() {
// do stuff
scope_exit{
// do stuff after func_impl is invoked
};
return static_cast<T&>(*this).func_impl();
}
Это дает вам правильный порядок операций, даже не требуя регулярной пустоты. Тем не менее, пост-func_impl
Логика ставится перед ним - что может сбивать с толку. Но выгода в том, что эта функция все еще может возвращать void
,
Есть множество реализаций таких вещей, как scope_exit
на ТАК.