You could create a helper function:
template <class T, class... Args>
bool is_in(const T& v, const Args&... args) {
return (... || (v == args)); // fold expression to check
}
int main(int argc, char**) {
return is_in(argc, 4, 6, 7);
}
The above program compiles into something like this with g++13.1
:
main:
lea eax, [rdi-6]
cmp eax, 1
setbe al
cmp edi, 4
sete dl
or eax, edx
movzx eax, al
ret
A version without writing a separate function template could use a lambda to do the same thing ...
int main(int argc, char**) {
return [&](auto&&... args){ return (... || (argc == args)); }(4, 6, 7);
}
... and it compiles into exactly the same thing as the function template instantiation with g++
.
If you, for example, populate a std::set
in order to do the same check you miss out on the short-circuit evaluation that the fold expression helps with. You also need to instantiate and populate the std::set
before making the query. This comes with some complexity that the compiler might not be able to optimize away.
Using a std::set
for the same query ...
#include <set>
int main(int argc, char**) {
return std::set{4, 6, 7}.contains(argc);
}
... and it compiles into something like this with the highest optimization enabled:
main:
push r15
xor r15d, r15d
push r14
mov r14d, edi
push r13
push r12
push rbp
push rbx
sub rsp, 72
mov rax, QWORD PTR .LC0[rip]
lea r13, [rsp+24]
mov rbp, rsp
mov DWORD PTR [rsp+8], 7
mov QWORD PTR [rsp], rax
mov DWORD PTR [rsp+24], 0
mov QWORD PTR [rsp+32], 0
mov QWORD PTR [rsp+40], r13
mov QWORD PTR [rsp+48], r13
mov QWORD PTR [rsp+56], 0
.L67:
mov r12d, DWORD PTR [rbp+0]
test r15, r15
je .L59
mov rbx, QWORD PTR [rsp+48]
cmp r12d, DWORD PTR [rbx+32]
jle .L59
.L60:
mov r15d, 1
cmp rbx, r13
jne .L92
.L66:
mov edi, 40
call operator new(unsigned long)
mov DWORD PTR [rax+32], r12d
mov rsi, rax
movzx edi, r15b
mov rcx, r13
mov rdx, rbx
call std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)
mov rax, QWORD PTR [rsp+56]
lea r15, [rax+1]
mov QWORD PTR [rsp+56], r15
.L65:
add rbp, 4
lea rax, [rsp+12]
cmp rbp, rax
jne .L67
mov rdi, QWORD PTR [rsp+32]
mov rbx, r13
mov rax, rdi
test rdi, rdi
jne .L68
jmp .L69
.L94:
mov rax, rcx
test rax, rax
je .L93
.L68:
mov rdx, QWORD PTR [rax+16]
mov rcx, QWORD PTR [rax+24]
cmp DWORD PTR [rax+32], r14d
jl .L94
mov rbx, rax
mov rax, rdx
test rax, rax
jne .L68
.L93:
cmp rbx, r13
je .L69
cmp DWORD PTR [rbx+32], r14d
jle .L69
mov rbx, r13
.L69:
call std::_Rb_tree<int, int, std::_Identity<int>, std::less<int>, std::allocator<int> >::_M_erase(std::_Rb_tree_node<int>*) [clone .isra.0]
xor eax, eax
cmp rbx, r13
setne al
add rsp, 72
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
ret
.L59:
mov rbx, QWORD PTR [rsp+32]
test rbx, rbx
jne .L63
jmp .L95
.L74:
mov rbx, rax
.L63:
cmp r12d, DWORD PTR [rbx+32]
mov rax, QWORD PTR [rbx+24]
cmovl rax, QWORD PTR [rbx+16]
setl dl
test rax, rax
jne .L74
mov rax, rbx
test dl, dl
jne .L96
.L64:
cmp r12d, DWORD PTR [rax+32]
jle .L65
jmp .L60
.L92:
cmp r12d, DWORD PTR [rbx+32]
setl r15b
jmp .L66
.L96:
cmp rbx, QWORD PTR [rsp+40]
je .L60
.L72:
mov rdi, rbx
call std::_Rb_tree_decrement(std::_Rb_tree_node_base*)
jmp .L64
.L95:
mov rbx, r13
cmp QWORD PTR [rsp+40], r13
jne .L72
mov r15d, 1
jmp .L66
mov rbx, rax
jmp .L70
main.cold:
.LC0:
.long 4
.long 6
Notice the call
s like call std::_Rb_tree_insert_and_rebalanc
which aren't necessarily cheap
For the sake of readability, don't use the lambda or the std::set
way.
For the sake of efficiency, don't use a std::set
in these situations.
Write a small function (or function template) with a good name that makes it clear what you're doing, or, if you only have three values to check, do the check explicitly:
if(i == 4 || i == 6 || i == 7) {
} else {
}
switch(i) {
case 4:
case 6:
case 7:
// do stuff
break;
default: ... // the else case
}