【问题标题】:What's the most C++ way to check if value belongs to certain static set?检查值是否属于某个静态集的最 C++ 方法是什么?
【发布时间】:2018-12-31 22:41:39
【问题描述】:

假设我想写这样的东西({1, 3, 7, 42, 69, 550123} 集合在编译之前是已知的):

int x;
...
if (x == 1 || x == 3 || x == 7 || x == 42 || x == 69 || x == 5550123)
{
  ...
}

条件看起来很难看,因为我们为每个可能的值添加了 9 个额外的符号(“|| x ==”)。如何以更 C++ 的方式重写它?

我的最佳猜测是:

int x;
...
const std::unordered_set<int> v = {1, 3, 7, 42, 69, 5550123};
if (v.count(x))
{
  ...
}

它的平均复杂度为 O(1),有一些内存和时间开销,但看起来还是有点难看。

【问题讨论】:

  • 我看不出你觉得第二个选项有什么难看的地方,但我想补充一点,鉴于这些数字是已知的,也许具有有序值的 const 向量可能会更好,那么你可以如果您的设置变大,请使用二分法,这将为您提供 O(log N)。
  • 这就是switch 语句的用途。
  • 我会说上面是非常惯用的,即使检查存在的语义是使用 count(...) &gt; 0 的元素不如例如contains(...),将在 C++20 中提供。
  • 大 O 表示法描述渐近行为;对于固定的或较小的输入大小是没有意义的。

标签: c++ set idioms syntactic-sugar


【解决方案1】:

编辑:我刚刚注意到 c++14 标签。请注意,我对 in 的实现依赖于 C++17。它也可以在 C++14 中使用递归来完成,但这涉及更多样板文件,并且编译速度有点慢。

可以使用模板生成具有逻辑运算符序列的函数,例如 nvoigt 的答案:

template<auto... ts, class T>
constexpr bool
in(const T& t) noexcept(noexcept(((t == ts) || ...))) {
    return ((t == ts) || ...);
}

// usage
if (in<1, 3, 7, 42, 69, 5550123>(x))

也就是说,将一组幻数隐藏在命名函数后面可能很有意义:

constexpr bool
is_magical(int x) noexcept {
    return in<1, 3, 7, 42, 69, 5550123>(x);
}

【讨论】:

  • 如果我使用 -O2 编译,“is_magical”的返回值将在编译期间预先计算,我说得对吗?
  • @Victor 编辑:只有在编译时知道x 并且函数被内联扩展时,才能预先计算结果。 -O2 确实启用了 inline-small-functions,但没有启用 inline-functions,我不知道什么时候认为函数很小。
  • constexpr 可能会添加到函数中。
  • OP 没有在问题正文中提到 C++14,因此,考虑到多个答案(也)如何建议 C++17 解决方案 - 我决定删除标签。另外,希望对我的方法提供反馈:-)
  • @einpoklum 反馈:它使用 PP 宏 :( 语法非常规。它不是 constexpr。它使用 std::initializer_list 导致复制,这可能是复杂类型的问题。除此之外,我看没问题。
【解决方案2】:

唯一干净的方法是简单地将它移动到一个方法中。 命名适当的方法,你在里面做什么并不重要。

bool is_valid_foo_number(int x)
{
    return x == 1
        || x == 3
        || x == 7
        || x == 42
        || x == 69
        || x == 5550123;
}

这个方法对我来说已经足够好了,因为我只会看到它

if (is_valid_foo_number(input))
{
    // ...
}

如果技术细节发生变化(例如大量有效数字需要另一种查找方法,或者可能是数据库而不是硬编码值),您需要更改方法的内部结构。

关键是我认为它只是看起来很丑......因为你需要在查看逻辑的同时查看它。反正你不应该看细节。

【讨论】:

  • 是的,如果我们谈论的是一个庞大的项目,那么您可能是对的,正确的格式和功能分离比高级功能和构造更好。
【解决方案3】:

如何以更 C++ 的方式重写它?

建议:为它制作一个可变参数模板函数,使其成为泛型(非仅用于int 值)并使其成为constexpr;这样你就可以检查集合中的值是否存在,编译时间。

您标记了 C++14,但我首先展示了 C++11 的递归方式,包括递归情况和基本情况

template <typename T>
constexpr bool is_in_list_11 (T const &)
 { return false; }

template <typename T, T t0, T ... ts>
constexpr bool is_in_list_11 (T const & t)
 { return (t == t0) || is_in_list_11<T, ts...>(t); }

从 C++14 开始,constexpr 函数可能要复杂得多,因此不再需要递归,您可以编写如下内容

template <typename T, T ... ts>
constexpr auto is_in_list_14 (T const & t)
 {
   using unused = bool[];

   bool ret { false };

   (void)unused { false, ret |= t == ts ... };

   return ret;
 }

从C++17开始你也可以使用auto模板类型和模板折叠,所以(就像user2079303之前展示的那样)函数变得非常简单

template <auto ... ts, typename T>
constexpr auto is_in_list_17 (T const & t)
 { return ( (t == ts) || ... ); }

以下是所有版本的完整工作示例

#include <iostream>

template <typename T>
constexpr bool is_in_list_11 (T const &)
 { return false; }

template <typename T, T t0, T ... ts>
constexpr bool is_in_list_11 (T const & t)
 { return (t == t0) || is_in_list_11<T, ts...>(t); }

template <typename T, T ... ts>
constexpr auto is_in_list_14 (T const & t)
 {
   using unused = bool[];

   bool ret { false };

   (void)unused { false, ret |= t == ts ... };

   return ret;
 }

template <auto ... ts, typename T>
constexpr auto is_in_list_17 (T const & t)
 { return ( (t == ts) || ... ); }

int main ()
 {
   constexpr auto b11a { is_in_list_11<int, 1, 3, 7, 42, 69, 5550123>(7) };
   constexpr auto b11b { is_in_list_11<int, 1, 3, 7, 42, 69, 5550123>(8) };

   constexpr auto b14a { is_in_list_14<int, 1, 3, 7, 42, 69, 5550123>(7) };
   constexpr auto b14b { is_in_list_14<int, 1, 3, 7, 42, 69, 5550123>(8) };

   constexpr auto b17a { is_in_list_17<1, 3, 7, 42, 69, 5550123>(7) };
   constexpr auto b17b { is_in_list_17<1, 3, 7, 42, 69, 5550123>(8) };

   std::cout << b11a << ' ' << b11b << std::endl;
   std::cout << b14a << ' ' << b14b << std::endl;
   std::cout << b17a << ' ' << b17b << std::endl;
 }

【讨论】:

    【解决方案4】:

    正如其他答案所说,将其移至函数。

    正如其他答案所说,您可以考虑根据需要添加 constexpr / throw。

    正如其他答案没有说的那样,为此使用 switch case 语句;它允许您用case 替换所有|| x == - 少几个字符,这可能看起来并不重要(而且它有点不重要);但最重要的是消除了变量名或| 出错的可能性。

    如果您在此列表中有 300 个项目,但它没有按预期工作,相信我,您会很高兴不必检查每个 ||是正确的。

    【讨论】:

      【解决方案5】:

      试试这个:

      #include <iostream>
      #include <vector>
      #include <algorithm>
      int main()
      {
        std::vector<int> v = {1, 3, 7, 42, 69, 5550123};
        auto is_present = [&v](int x)->bool{
            return  std::find(v.begin(),v.end(),x) != v.end();
        };      
        std::cout << (is_present(1)? "present" :" no present") <<std::endl;       
      }
      

      【讨论】:

      • std::findstd::binary_search 可以用来代替手卷循环。
      • @seccpur 您可以使用 std::array 代替向量,因为长度和数字是已知的。
      • if (expr) return true; return false; 等价于return expr;
      • 对于一组固定的项目,您可能希望使用std::array 而不是std::vector
      【解决方案6】:

      这是我的建议。它可以让你写:

      int x;
      // ...
      if ( x is_one_of {1, 3, 7, 42, 69, 550123} ) {
          // ...
      }    
      

      这几乎是您能想到的最直接的方式。


      但是 - 这是什么黑暗魔法?我肯定是在作弊吧?

      嗯,不。或者,不是真的。从某种意义上说,这个解决方案是“最 C++”的解决方案,它采用了多种技巧,包括丑陋的技巧,C++ 让您可以自定义其有效语法,远远超出您的预期!

      这是 C++17 中的实现:

      #include <functional>
      
      namespace detail {
      
      struct is_one_of_obj {};
      
      template <typename T>
      struct is_one_of_obj_primed { 
          T&& t; 
      
          template <typename... Us>
          constexpr bool operator+(std::initializer_list<Us...>&& list) {
              for(const auto& element : list) {
                  if (t == element) { return true; }
              }
              return false;
          }
      };
      
      template <typename T>
      constexpr is_one_of_obj_primed<T> operator+(T&& t, is_one_of_obj)
      {
          return is_one_of_obj_primed<T>{std::forward<T>(t)};
      }
      
      } // namespace detail
      
      #define is_one_of + detail::is_one_of_obj{} + std::initializer_list
      

      查看实际操作:GodBolt

      注意事项:

      • 此实现的灵感部分归功于 SO 上的this answer
      • 警告:值必须具有相同的类型,否则会发生坏事>:-)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-12-20
        • 2012-02-02
        • 2016-11-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多