избегайте написания того же повторяющегося кода проверки типа с помощью std :: any - PullRequest
0 голосов
/ 29 августа 2018

Я хочу использовать std :: any в моей программе, но я пишу много условных выражений, таких как:

   if (anything.type() == typeid(short)) {
      auto s = std::any_cast<short>(anything);
   } else if (anything.type() == typeid(int)) {
      auto i = std::any_cast<int>(anything);
   } else if (anything.type() == typeid(long)) {
      auto l = std::any_cast<long>(anything);
   } else if (anything.type() == typeid(double)) {
      auto d = std::any_cast<double>(anything);
   } else if (anything.type() == typeid(bool)) {
      auto b = std::any_cast<bool>(anything);
   } 

Обратите внимание, что я пропустил большую часть остальных условий для краткости.

Моя программа может использовать любой из определенных типов, которые могут храниться в std :: any, поэтому эти операторы if-then довольно длинные. Есть ли способ реорганизовать код, чтобы я мог написать его один раз?

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

template<typename T>
T AnyCastFunction(std::any) {

   T type;
   if (anything.type() == typeid(short)) {
      type = std::any_cast<short>(anything);
   } else if (anything.type() == typeid(int)) {
      type = std::any_cast<int>(anything);
   } else if (anything.type() == typeid(long)) {
      type = std::any_cast<long>(anything);
   } else if (anything.type() == typeid(double)) {
      type = std::any_cast<double>(anything);
   } else if (anything.type() == typeid(bool)) {
      type = std::any_cast<bool>(anything);
   } 

   return type;
}

Однако это приводит к ошибкам «невозможно вывести параметр шаблона T». Как я могу реорганизовать это, чтобы избежать записи больших блоков if / else много раз по всей программе?

Ответы [ 4 ]

0 голосов
/ 30 августа 2018

Я нахожу этот тип кода интересным для написания.

any_visitor<types...> - это функциональный объект, который посещает набор типов.

Вы вызываете его с любым, за которым следует объект функции. Затем он вызывает функциональный объект в зависимости от того, какой из types... находится в any.

Итак, вы делаете any_vistor<int, double>{}( something, [](auto&& x) { /* some code */ } ).

Если ни один из types... не находится в any, он вызывает функциональный объект с std::any для вас, чтобы иметь дело с дополнительным регистром.

Мы также можем написать вариант, который вместо передачи std::any функтору генерирует или возвращает false или что-то в этом роде.

template<class...Ts>
struct any_visitor;

template<>
struct any_visitor<> {
    template<class F>
    decltype(auto) operator()( std::any& a, F&& f ) const {
        return std::forward<F>(f)(a);
    }
};

template<class...Ts>
struct any_visitor {
private:
    struct accum {
        std::size_t x = 0;
        friend accum operator+( accum lhs, accum rhs ) {
            if (lhs.x || rhs.x) return {lhs.x+1};
            else return {};
        }
    };
public:
    template<class Any, class F>
    void operator()(Any&& any, F&& f) const {
        // sizeof...(Ts) none in the list
        // otherwise, index of which any is in the list
        std::size_t which = sizeof...(Ts) - (accum{} + ... + accum{ any.type() == typeid(Ts) }).x;

        using table_entry = void(*)(Any&&, F&&);

        static const table_entry table[] = {
            +[](Any&& any, F&& f) {
                std::forward<F>(f)( std::any_cast<Ts>( std::forward<Any>(any) ) );
            }...,
            +[](Any&& any, F&& f) {
                std::forward<F>(f)( std::forward<Any>(any) );
            }
        };

        table[which]( std::forward<Any>(any), std::forward<F>(f) );
    }
};

template<class...Fs>
struct overloaded:Fs... {
    using Fs::operator()...;
};
template<class...Fs>
overloaded(Fs&&...)->overloaded<std::decay_t<Fs>...>;

Я также включил overloaded, что облегчает отправку. Если вы хотите обрабатывать все типы одинаково, за исключением случая ошибки, вы можете сделать:

overloaded{
  [](auto const& x){ std::cout << x << "\n"; },
  [](std::any const&){ std::cout << "Unknown type\n"; }
}

и передать это как функциональный объект any_visitor.

Вот тестовый код:

std::any foo=7;
std::any bar=3.14;

auto visitor = overloaded{
    [](int x){std::cout << x << "\n";},
    [](auto&&){std::cout << "Unknown\n";}
};
any_visitor<int>{}( foo, visitor );
any_visitor<int>{}( bar, visitor );

который выводит:

7
Unknown

Живой пример .

В реализации, этот код использует таблицу диспетчеризации (вроде как в vtable) для отображения индекса типа, хранящегося в объекте any, для которого вызывается перегрузка объекта функции.


Еще одним подходом было бы написать:

template<class...Ts>
std::optional<std::variant<Ts...>> to_variant( std::any );

, который преобразует std::any в вариант, если его типы совпадают. Затем используйте обычную машину для посещения на std::variant вместо того, чтобы кататься на собственной.

0 голосов
/ 30 августа 2018

Основная идея - создать посетителя std::any и выполнить необходимую обработку в функции, вызываемой из посетителя. Этот основной принцип прост. Начнем с поддержки только одного типа:

#include <any>
#include <iostream>
#include <type_traits>

template <typename T, typename Any, typename Visitor>
auto any_visit1(Any&& any, Visitor visit)
    -> std::enable_if_t<std::is_same_v<std::any, std::decay_t<Any>>>
{
    if (any.type() == typeid(T)) {
        visit(std::any_cast<T>(std::forward<Any>(any)));
    }
}

int main() {
    std::any a0(17);

    any_visit1<int>(a0, [](auto value){ std::cout << "value=" << value << "\n"; });
}

Следующий шаг - снять ограничение одного типа. Поскольку явные параметры шаблона идут первыми и представляют собой открытый список, а объект функции должен быть выведенным параметром шаблона, вы не можете использовать шаблон функции. Тем не менее, шаблон переменной (с inline constexpr, конечно, следовательно, переменная ...) добивается цели:

#include <any>
#include <iostream>
#include <type_traits>

template <typename... T>
inline constexpr auto any_visit =
    [](auto&& any, auto visit) -> std::enable_if_t<std::is_same_v<std::any, std::decay_t<decltype(any)>>> {
    (
    (any.type() == typeid(T) && (visit(std::any_cast<T>(std::forward<decltype(any)>(any))), true))
    || ...)
    // Uncomment the line below to have visit(any) called for unhandled types
    // || (visit(std::forward<decltype(any)>(any)), true)
    ;
};

void test(std::any any)
{
    any_visit<int, double, char const*>(any, [](auto value){ std::cout << "value=" << value << "\n"; });
}

int main() {
    test(17);
    test(3.14);
    test(+"foo");
}

Если вам нужно декодировать несколько std::any объектов, вы просто передадите в него подходящие функции [лямбда?], Которые ссылаются на другие объекты, и продолжаете создавать объект, пока не получите все те, которые вам нужны.

0 голосов
/ 30 августа 2018

Если у вас есть фиксированный список возможных типов, не используйте std::any. Используйте std::variant<Ts...>. Это ответ Дитмара выглядит следующим образом:

#include <variant>

void test(std::variant<int, double, char const*> v)
{
    std::visit([](auto value){ std::cout << "value=" << value << "\n"; }, v);
}

это то же самое, за исключением того, что (а) вам не нужно реализовывать visit самостоятельно (б) это значительно более эффективно во время выполнения и (в) это безопасно для типов - вы не можете забыть проверить определенный тип! На самом деле, даже если вас не волнует (а) или (б), (в) это огромный выигрыш.

И если у вас нет известного, фиксированного списка возможных типов - который является типичным вариантом использования std::any - тогда все, что вы делаете с std::any, не имеет все равно не имеет смысла. Вы не можете перечислить все возможные копируемые типы (их бесконечное количество), поэтому вы не можете извлекать содержимое. Поэтому я действительно думаю, что variant - это то, что вы хотите.

0 голосов
/ 29 августа 2018

Ну, если вы уверены, что вам нужен такой широкий диапазон, хранящийся в any ...

template<typename T> void visit(T &&t) { std::cout << "Hi " << t << "!\n"; }

void try_visit(std::any &&) { std::cout << "Unknown type\n"; }

template<typename T, typename... Ts> void try_visit(std::any thing) {
     if(thing.type() == typeid(T)) {
         visit(std::any_cast<T>(thing));
         return;
     }
     if constexpr(sizeof...(Ts) > 0) try_visit<Ts...>(std::move(thing));
     else try_visit(std::move(thing));
}

int main() {
    try_visit<short, int, double, bool, long>(std::any{42});
}

% -}

...