Я реализую простой форматировщик текста для класса. Основная функция в нем может получить список значений, которые будут объединены. Или, опционально, для случаев, когда параметры не являются друзьями ostream, я принимаю функцию преобразования в качестве первого параметра, которая преобразует все остальные параметры в std :: string.

Следующий код показывает идею, но не компилируется. Для простоты я выведу на cout в примере.

struct formater{
    template<typename P, typename... PS>
    void format(const P& p, const PS&... ps){
        if (std::is_convertible<P, std::function<void()>>::value){
            cout << p(ps...) << endl;
        } else {
            cout << p;
            log(ps...);
        }
    }
};

Причина, по которой код не компилируется, состоит в том, что если P вызывается, то невозможно вывести его в cout в ветви «else», а если P не вызывается, он скажет, что P не вызывается и не может получить ps. .. параметры в "тогда" ветке.

Я думал использовать enable_if, но так как я определяю оба случая (T и F) условия, я получаю переопределение одной и той же функции и также не могу скомпилировать.

Я мог бы попытаться подражать static_if, но это не выглядит изящно вообще.

Мне интересно, есть ли какой-нибудь элегантный способ проверить, что P вызывается и СФИНАЕВАЕТ его. Возможно, используя это, я знаю типы параметров P, (PS ...) -> std :: string.

4
dvicino 3 Апр 2017 в 17:07

2 ответа

Лучший ответ

Есть две проблемы. Во-первых, как вы сказали, if - это только ветвь времени выполнения, а во-вторых, вы не проверяете объект, который можно вызвать, но вы проверяете, можно ли вызвать объект без аргументов. Любой вызываемый объект, который должен принимать аргументы, не пройдет ваш тест.

Сначала я реализовал бы эту черту. Обратите внимание, что это не обязательно для C ++ 17:

template<typename, typename = void>
struct is_callable : std::false_type {};

template<typename F, typename... Args>
struct is_callable<F(Args...), void_t<decltype(std::declval<F>()(std::declval<Args>()...))>> : std::true_type {};

Затем вы можете использовать std::enable_if:

struct Formatter {
    template<typename F, typename... Args>
    auto format(F func, Args&&... args) -> std::enable_if_t<is_callable_v<F(Args...)>> {
        std::cout << func(std::forward<Args>(args)...);
        std::cout << std::endl;
    }

    template<typename T>
    auto format(T&& value) -> std::enable_if_t<!is_callable_v<T()>> {
        std::cout << std::forward<T>(value);
        std::cout << std::endl;
    }
};

Вы можете реализовать void_t следующим образом:

template<typename...>
using void_t = void;

Вы можете проверить этот пример в реальном времени: Live at coliru

Обратите внимание, что в C ++ 17 у вас есть constexpr if и std::is_invocable:

struct Formatter {
    template<typename F, typename... Args
    void format(F&& param, Args&&... args) {
        if constexpr (std::is_invocable_v<F, Args...>) {
            std::cout << std::invoke(std::forward<F>(param), std::forward<Args>(args)...);
        } else {
            std::cout << std::forward<F>(param);
            log(std::forward<Args>(args)...);
        }
    }
};
4
Guillaume Racicot 4 Апр 2017 в 01:12

Вы можете использовать std::enable_if:

struct formater{
    template<typename P, typename... PS>
    std::enable_if<std::is_convertible<PARAM, std::function<void(PS...)>>::value, void>::type
    format(const PARAM& p, const PS&... ps){
        cout << p(ps...) << endl;

    }

    template<typename P, typename... PS>
    std::enable_if<!std::is_convertible<PARAM, std::function<void(PS...)>>::value, void>::type
    format(const PARAM& p, const PS&... ps){
        cout << p;
        log(ps...);

    }
};
1
Benjamin T 3 Апр 2017 в 15:01