Multid std: вариант посещения с помощью variadi c templated helper - PullRequest
6 голосов
/ 06 апреля 2020

Я пытаюсь создать функцию, которая помогает обрабатывать N std::variant типов.

Примечание: Я пытаюсь сделать все пути компиляции валидированными. Поэтому std::optional и std::holds_alternative для меня нежизнеспособны.

Реализация выглядит следующим образом:

template<typename T>
using Possible = std::variant<std::monostate, T>;

template<typename... Types>
void ifAll(std::function<void(Types...)> all, Possible<Types>&&... possibles)
{
    std::visit(
        [&](auto&&... args) {
            if constexpr ((... &&
                           std::is_same_v<std::decay_t<decltype(args)>, Types>))
            {
                return all(std::forward<Types>(args)...);
            }
            else
            {
                std::cout << "At least one type is monostate" << std::endl;
            }
        },
        possibles...);
}

И пример использования функции:

int main()
{
    Possible<int>  a = 16;
    Possible<bool> b = true;

    ifAll([](const int& x, const bool& y)
              -> void { std::cout << "All types set!" << std::endl; },
          a,
          b);
}

Однако я получаю ошибку компилятора:

TestFile.cc: error: no matching function for call to 'ifAll'
    ifAll([](const int& x, const bool& y)
    ^~~~~

TestFile.cc: note: candidate template ignored: could not match
    'function<void (type-parameter-0-0...)>' against '(lambda at
    TestFile.cc)'

void ifAll(std::function<void(Types...)> all, Possible<Types>&&... possibles)
    ^

Почему предоставляемая мной лямбда не соответствует сигнатуре функции?

Попытка исправить 1

Я попытался переместиться в a и b, которые все еще не работают:

ifAll([](const int& x, const bool& y)
              -> void { std::cout << "All types set!" << std::endl; },
          std::move(a),
          std::move(b));

Ответы [ 5 ]

3 голосов
/ 06 апреля 2020

Будет работать следующий вызов:

int main() {
    Possible<int>  a = 16;
    Possible<bool> b = true;

    std::function<void(int, bool)> fun = [](int x, bool y) -> void {
        std::cout << "All types set!" << std::endl;
    };

    ifAll(fun,
          std::move(a),
          std::move(b));
}

или переключить подпись вашей функции на:

template <typename... Types>
void ifAll(std::function<void(Types...)> const& all, Possible<Types>&... possibles)

, и тогда вы сможете позвонить без std::move:

int main() {
    Possible<int>  a = 16;
    Possible<bool> b = true;

    std::function<void(int, bool)> fun = [](int x, bool y) -> void {
        std::cout << "All types set!" << std::endl;
    };

    ifAll(fun, a, b);
}
1 голос
/ 06 апреля 2020

Possible<Types>&& фактически является ссылкой rvalue, а не ссылкой пересылки. Вы должны добавить перегрузки для обработки различных случаев.

template<F, typename... Types> void ifAll(F, const Possible<Types>&...);
template<F, typename... Types> void ifAll(F, Possible<Types>&...);
template<F, typename... Types> void ifAll(F, Possible<Types>&&...);

В template<typename... Types> void ifAll(std::function<void(Types...)>, Possible<Types>&...), Types должен быть выведен дважды, поэтому ошибка возникает, когда вычет не совпадает.

В В вашем случае у вас сначала const int&, const bool& (начиная с CTAD с C ++ 17), а затем int, bool. Несоответствие, поэтому ошибка.

Несколько способов решения проблем:

  • Исправление вызова сайта (fr agile решение):

    std::function<int, bool> f = [](const int& x, const bool& y)
                  -> void { std::cout << "All types set!" << std::endl; };
    ifAll(fun, a, b); // Assuming overload with lvalue references
    
  • Сделать один параметр не подлежащим вычету:

    template<typename... Types>
    void ifAll(std::function<void(std::identity_type_t<Types>...)>, Possible<Types>&...)
    
  • Добавить дополнительные параметры шаблона:

    template<typename... Args, typename... Types>
    void ifAll(std::function<void(Args...)>, Possible<Types>&...)
    

    Возможно с некоторым SFINAE.

  • Измените полностью аргумент (я бы go для одного):

    template<F, typename... Types>
    void ifAll(F, Possible<Types>&...)
    // or even
    template<F, typename... Ts>
    void ifAll(F, Ts&&...); // Forwarding reference, no extra overloads to add.
    

    Возможно, с некоторым SFINAE.

1 голос
/ 06 апреля 2020

Мои извинения. Я считаю, что проблема довольно двоякая:

  1. Вы не используете std :: move для передачи значений lvalue (переменные a, b) в функцию, принимающую аргументы xvalue (Possible<Types>&&...) расширение.)
  2. Вы пытаетесь преобразовать std::function<void(const int&, const int&)> в std::function<void(int, int)> при передаче лямбда-функции в вашу функцию.

Компилятор сопоставляет Types с (int, int), если другой аргументы. Затем он пытается найти std::function<void(int, int)> в качестве первого аргумента. Вместо этого он получает функцию лямбда типа void(*)(const int&, const int&). Таким образом, существует несоответствие сигнатур.

Мое предложение состояло бы в том, чтобы взять подсказку из стандартной библиотеки и вместо попытки использовать объекты std :: function указанного c типа, вместо этого добавить параметр шаблона FuncType для типа функции, и передайте указатель на функцию, используя это. Я думаю, именно поэтому стандартные алгоритмы принимают тип функции в качестве параметра шаблона, даже если они могут определить приблизительную сигнатуру функции, которая должна быть передана из других аргументов шаблона.

1 голос
/ 06 апреля 2020

Простым решением является использование функционального объекта + std::optional:

#include <functional>
#include <optional>

struct Error {};

template <typename F, typename... Args>
decltype(auto) if_all(F&& f, Args&&... args)
{
    if ((args && ...)) {
        return std::invoke(std::forward<F>(f), *std::forward<Args>(args)...);
    } else {
        throw Error{};
    }
}

Пример использования:

#include <functional>
#include <iostream>

int main()
{
    std::optional<int> a{5};
    std::optional<int> b{10};
    std::cout << if_all(std::plus{}, a, b) << '\n';
}

( live demo )

Если вы настаиваете на использовании std::variant вместо std::optional (что, вероятно, связано с некоторыми недоразумениями в отношении любого из них), идея та же самая - сначала нужно проверить, все ли аргументы «пусты» ( возможно, используя std::holds_alternative), и разверните аргументы после.

0 голосов
/ 06 апреля 2020

Как и в принятом ответе, одна проблема заключается в том, что у вас не должно быть && в Possible<Types>&&. Это означает, что вы можете принимать только аргументы rvalue, но в вашем примере использования a и b являются lvalue. Самый простой способ - использовать пересылочные ссылки для обработки всех дел. Другая проблема заключается в том, что вы разрешаете вывод Types... из аргумента std::function. Возможно, что аргументы лямбды не совсем совпадают с Possible с, и поэтому вы должны предотвратить вывод Types... из std::function и взять их только из Possible с. Самый простой способ сделать это - вставить какое-то вычисление типа в аргумент шаблона в типе аргумента, что заставит компилятор отказаться от попыток сделать дедукцию для этого аргумента. Иногда вы просто используете фиктивную функцию type_identity_t, но здесь мы можем сделать что-то более интересное и получить обеих птиц одним камнем

template<typename T>
struct get_handler_argument;
template<typename T>
struct get_handler_argument<Possible<T>> { using type = T&; };
template<typename T>
struct get_handler_argument<Possible<T> const> { using type = T const&; };
template<typename T>
using get_handler_argument_t = typename get_handler_argument<std::remove_reference_t<T>>::type;

template<typename... Vars>
void ifAll(std::function<void(get_handler_argument_t<Vars>...)> all, Vars&&... possibles) {
    std::visit(
        [&](auto&&... args) {
            if constexpr ((... &&
                           std::is_same_v<std::decay_t<decltype(args)>, std::decay_t<get_handler_argument_t<Vars>>>)) {
                return all(std::forward<get_handler_argument_t<Vars>>(args)...);
            } else {
                std::cout << "At least one type is monostate" << std::endl;
            }
        },
        possibles...);
}

То, что вы теряете здесь, это то, что вы больше не можете напрямую предоставлять «текущее» значение вместо значения Possible без явного переноса его в конструктор Possible. Это не потеря; просто захватите это вместо того, чтобы передать это. Вы получаете то, что вы можете использовать вызов функции или что у вас есть для инициализации Possible вместо того, чтобы сначала сохранять их в переменных вручную. Ваш пример работает как есть с этим определением, в отличие от принятого в настоящее время ответа.

...