检查值是否属于某个静态集的最 C++ 方法是什么?
What's the most C++ way to check if value belongs to certain static set?
假设我想写这样的东西({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),有一些内存和时间开销,但看起来还是有点丑。
执行此操作的唯一干净方法是将其简单地移动到一个方法中。 适当地命名方法,你在里面做什么并不重要。
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))
{
// ...
}
如果技术细节发生变化(例如需要另一种查找方法的有效数字的绝对数量,或者可能是数据库而不是硬编码值),您可以更改方法的内部结构。
关键是我觉得它只是看起来很丑...因为你需要一边看你的逻辑一边看它。无论如何你都不应该看细节。
编辑:我刚刚注意到 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);
}
How can I rewrite it in a more C++ way?
建议:为它制作一个可变参数模板函数,使其通用(不仅适用于 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;
}
试试这个:
#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;
}
正如其他答案所说,将其移至函数。
如其他答案所说,您可以考虑根据需要添加constexpr / throw。
因为其他答案没有说,为此使用switch case语句;这允许您将所有 || x ==
替换为 case
- 少了几个字符,这可能看起来并不重要(而且有点不重要);但最重要的是消除了变量名或 |
.
出错的可能性
当您在此列表中有 300 项,并且它没有按预期工作时,相信我,您会很高兴不必检查每个 ||是正确的。
这是我的建议。它可以让你写:
int x;
// ...
if ( x is_one_of {1, 3, 7, 42, 69, 550123} ) {
// ...
}
这几乎是您能想到的最直接的方式。
但是 - 这是什么黑魔法?肯定是我作弊了吧?
嗯,不。或者,不是真的。从这个意义上说,这个解决方案是“最 C++”的,因为它采用了多种 hack(包括丑陋的 hack),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
备注:
- 此实现的灵感部分归功于 this answer here on SO。
- 警告:值必须全部具有相同的类型,否则会发生不好的事情 >:-)
假设我想写这样的东西({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),有一些内存和时间开销,但看起来还是有点丑。
执行此操作的唯一干净方法是将其简单地移动到一个方法中。 适当地命名方法,你在里面做什么并不重要。
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))
{
// ...
}
如果技术细节发生变化(例如需要另一种查找方法的有效数字的绝对数量,或者可能是数据库而不是硬编码值),您可以更改方法的内部结构。
关键是我觉得它只是看起来很丑...因为你需要一边看你的逻辑一边看它。无论如何你都不应该看细节。
编辑:我刚刚注意到 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);
}
How can I rewrite it in a more C++ way?
建议:为它制作一个可变参数模板函数,使其通用(不仅适用于 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;
}
试试这个:
#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;
}
正如其他答案所说,将其移至函数。
如其他答案所说,您可以考虑根据需要添加constexpr / throw。
因为其他答案没有说,为此使用switch case语句;这允许您将所有 || x ==
替换为 case
- 少了几个字符,这可能看起来并不重要(而且有点不重要);但最重要的是消除了变量名或 |
.
当您在此列表中有 300 项,并且它没有按预期工作时,相信我,您会很高兴不必检查每个 ||是正确的。
这是我的建议。它可以让你写:
int x;
// ...
if ( x is_one_of {1, 3, 7, 42, 69, 550123} ) {
// ...
}
这几乎是您能想到的最直接的方式。
但是 - 这是什么黑魔法?肯定是我作弊了吧?
嗯,不。或者,不是真的。从这个意义上说,这个解决方案是“最 C++”的,因为它采用了多种 hack(包括丑陋的 hack),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
备注:
- 此实现的灵感部分归功于 this answer here on SO。
- 警告:值必须全部具有相同的类型,否则会发生不好的事情 >:-)