避免使用std :: any编写相同的重复类型检查代码

时间:2018-08-29 20:24:02

标签: c++ c++14 c++17

我想在程序中使用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 :(得分:3)

如果您有已知的可能类型的固定列表,请不要使用std::any。使用std::variant<Ts...>。这使得Dietmar's的答案看起来像这样:

#include <variant>

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

这是同一件事,除了(a)您不必自己实现visit(b)运行时效率更高,并且(c)类型安全-您不会忘记检查特定类型!真的,即使您不在乎(a)或(b),(c)也是一个巨大的胜利。

如果您没有有一个已知的,可能的类型的固定列表-这是想要std::any的典型用例-那么您在{{ 1}}毫无意义。您无法枚举所有可能的可复制类型(有无限数量的可复制类型),因此不必检索内容。所以我真的认为std::any是您想要的。

答案 1 :(得分:1)

好吧,如果您确定需要存储在 ...... p

angular
  .module('myApp', [])
  .controller('myController', ($scope, $timeout, $interval) => {
    var interval;
    var timeout;
    $scope.times = 0;
    $scope.mouseUp = function(){
      $timeout.cancel(timeout);
      $interval.cancel(interval);
    };
    let increment = ()=>{
      $scope.times++;
    };
    $scope.mouseDown = function(){
      increment();
      timeout = $timeout(()=>{
        interval = $interval(()=>{
          increment();
        },100)
      }, 1000)
      
    };

  });

%-}

答案 2 :(得分:1)

基本思想是创建一个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,因此是 variable ...)可以达到目的:

#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个对象,您只需向其中传递适当的[lambda?]函数即可,该函数引用其他对象并不断构建该对象,直到获得所需的所有对象为止。

答案 3 :(得分:1)

我觉得编写这种代码很有趣。

(defclass myclass () ((s1 :initform '((a . 1) (b . 2))) (s2 :initform '((a . 1) (b . 3))))) 是访问一组类型的函数对象。

您可以先调用any,然后再调用功能对象。然后,它将使用any_visitor<types...>中的types...中的任何一个调用函数对象。

您这样做了any

如果any_vistor<int, double>{}( something, [](auto&& x) { /* some code */ } )中都没有types...,它将用any调用函数对象,以处理多余的情况。

我们还可以编写一个变体,而不是将std::any传递给函子,而是抛出或返回false或其他内容。

std::any

我还添加了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 );

Live example

在实现上很明智,此代码使用分派表(类似于vtable)来映射存储在我们调用的函数对象的any中存储的类型的索引。


另一种方法是编写:

7
Unknown

,如果类型匹配,它将template<class...Ts> std::optional<std::variant<Ts...>> to_variant( std::any ); 转换为变量。然后在std::any上使用通常的访问机制,而不用自己动手。