【问题标题】:avoid writing the same repetitive type-checking code with std::any避免使用 std::any 编写相同的重复类型检查代码
【发布时间】:2018-08-29 20:24:02
【问题描述】:

我想在我的程序中使用 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);
   } 

请注意,为简洁起见,我省略了很多 else if 条件。

我的程序可以使用任何可以存储在 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 块?

【问题讨论】:

  • 你确定std::any是你想要的,而不是std::variant&lt;short,int,long,double,bool&gt;吗?
  • 我认为有效的答案可能涉及更广泛地了解您的架构。首先,您如何以及为什么使用std::any 这么多?当您不提前知道结果是什么或它将做什么/支持什么时,您是如何使用结果的?
  • 这似乎是您想要从强类型语言中删除类型检查。我会质疑这样做的设计。
  • @JerryCoffin 试图构建一个可以接受自定义配置文件的测试框架。我无法真正预测需要什么类型等。
  • @DietmarKühl 这听起来很有趣。您能否提供更多细节?

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


【解决方案1】:

如果您有一个已知的、固定的可能类型列表,请不要使用std::any。使用std::variant&lt;Ts...&gt;。这使得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 的典型用例 - 那么你对 std::any 所做的任何事情都不会无论如何都没有意义。您无法枚举所有可能的可复制类型(它们的数量是无限的),因此您不一定能检索到内容。所以我真的认为variant 是你想要的。

【讨论】:

  • 我同意使用std::variant&lt;T...&gt; 是要走的路。不过,我确实喜欢变量模板的技巧,让可变参数列表先出现,并且非常喜欢敲出这段代码。
  • @DietmarKühl 是的,写起来很有趣:-)
【解决方案2】:

好吧,如果您确定需要存储在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});
}

%-}

【讨论】:

  • 现在是多个任意的:P
  • for(auto &amp;&amp;t: {std::any{42}, std::any{3.14}, std::any{"lo"}, std::any{}}) try_visit&lt;short, int, double, bool, long&gt;(std::move(t)); 8-)
  • 他的意思是try_visit&lt;short, int, double, bool, long&gt;(std::any{42}, std::any{3.14});
  • 您的递归看起来不太正确。即,我认为您的代码无法编译。
  • @SombreroChicken 需要定义一个额外的结构。 >_>
【解决方案3】:

我觉得这种类型的代码写起来很有趣。

any_visitor&lt;types...&gt;是访问一组类型的函数对象。

您可以使用 any 后跟一个函数对象来调用它。然后它使用types... 中的any 中的任何一个来调用函数对象。

所以你做any_vistor&lt;int, double&gt;{}( something, [](auto&amp;&amp; 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

Live example.

在实现方面,此代码使用调度表(有点像 vtable)来映射存储在我们调用的函数对象重载的 any 中的类型的索引。


另一种方法是写:

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

如果其类型匹配,则将std::any 转换为变体。然后在std::variant 上使用通常的访问机器,而不是自己滚动。

【讨论】:

    【解决方案4】:

    基本思想是创建一个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?] 函数传递给其中引用其他对象并继续构建对象,直到获得所需的所有对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-11
      • 1970-01-01
      相关资源
      最近更新 更多