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 на ТАК.

Другие вопросы по тегам