Yes, it makes sense. The alternative, [[likely]], path is the one where the condition is false, that is, the path not calling do_stuff();. That becomes the path it'll try to optimize for.
Example:
#include <iostream>
inline void do_stuff() {
std::cout << "Surprise!\n";
}
int main(int argc, char**) {
if (argc == 0) [[likely]] {
do_stuff();
}
}
Assembler with [[likely]]:
.LC0:
.string "Surprise!\n"
main:
test edi, edi
jne .L4
sub rsp, 8
mov edx, 10
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
xor eax, eax
add rsp, 8
ret
.L4:
xor eax, eax
ret
_GLOBAL__sub_I_main:
sub rsp, 8
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
add rsp, 8
jmp __cxa_atexit
With [[unlikely]] (and without attribute at all):
.LC0:
.string "Surprise!\n"
main:
test edi, edi
je .L8
xor eax, eax
ret
.L8:
push rax
mov edx, 10
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
xor eax, eax
pop rdx
ret
_GLOBAL__sub_I_main:
sub rsp, 8
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
add rsp, 8
jmp __cxa_atexit
Those are two slightly different outcomes and without knowing too much about assembler, I'd say the effect of putting [[likely]] there is clear. It looks to me that putting [[likely]] there made it inline the function while [[unlikely]] left it as a function call.