Generically convert bytes to integers
The function signature strikes me as difficult to read, thanks to the constraint std::same_as&lt;std::byte&gt; auto... and the trailing &quot;return type&quot; std::unsigned_integral auto. I might rather write something like
constexpr auto bytes_to_uint(std::initializer_list&lt;std::byte&gt; bytes) {

...Ah, but then you couldn't use bytes.size() as a constant expression; I see.
So I'd think about writing an overload set, like this:
constexpr std::uint8_t bytes_to_uint(std::byte a) {
    return a;
}
constexpr std::uint16_t bytes_to_uint(std::byte a, std::byte b) {
    return (a &lt;&lt; 8) | b;
}
constexpr std::uint32_t bytes_to_uint(std::byte a, std::byte b, std::byte c, std::byte d) {
    return (a &lt;&lt; 24) | (b &lt;&lt; 16) | (c &lt;&lt; 8) | d;
}

But I guess this is messy because you need 16 different overloads. You can't even use default function arguments, because you want bytes_to_uint(a,b,c) to be equal to bytes_to_uint(0,a,b,c) and not bytes_to_uint(a,b,c,0). Of course you could still write
#define B std::byte
constexpr std::uint8_t bytes_to_uint(B a)
  { return bytes_to_uint(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,a); }
constexpr std::uint16_t bytes_to_uint(B a, B b)
  { return bytes_to_uint(0,0,0,0,0,0,0,0,0,0,0,0,0,0,a,b); }
constexpr std::uint32_t bytes_to_uint(B a, B b, B c)
  { return bytes_to_uint(0,0,0,0,0,0,0,0,0,0,0,0,0,a,b,c); }
constexpr std::uint32_t bytes_to_uint(B a, B b, B c, B d)
  { return bytes_to_uint(0,0,0,0,0,0,0,0,0,0,0,0,a,b,c,d); }
constexpr std::uint64_t bytes_to_uint(B a, B b, B c, B d, B e)
  { return bytes_to_uint(0,0,0,0,0,0,0,0,0,0,0,a,b,c,d,e); }
[...22 more lines...]
#undef B

but I bet you don't want to do that. Okay, let's stick with the templatey thing you did.

using result = std::tuple_element_t&lt;N, types&gt;;

I'd rather see this dependent typedef use CamelCase (like a template parameter) or suffixedwith_type (like an STL member typedef). Calling it result makes it look too much like a variable, and makes it hard to pick out the one place where you use it.
Instead of spending 13 lines and a &lt;tuple&gt; dependency, I'd rather just do
using ResultType = std::conditional_t&lt;
    (N == 1), std::uint8_t, std::conditional_t&lt;
    (N == 2), std::uint16_t, std::conditional_t&lt;
    (N &lt;= 4), std::uint32_t, std::uint64_t&gt;&gt;&gt;;

Which reminds me, you need something like
static_assert(N &lt;= 16);

to stop you from trying to handle an argument list of 17 bytes or more.
And I didn't even notice until I tried it in Godbolt, but, you've got an off-by-one bug here! You meant tuple_element_t&lt;N-1, types&gt;. Remember that indexing always starts at zero (everywhere except &lt;regex&gt;).

If you don't like conditional_t, another option is to use plain old ifs. Factor out the computation while it's still parameterized on ResultType, and then use an if-else chain to decide on the right type to plug in for ResultType later:
auto do_it = [&amp;]&lt;class ResultType, std::size_t... S&gt;(ResultType, std::index_sequence&lt;S...&gt;) {
    return ((static_cast&lt;ResultType&gt;(bytes) &lt;&lt; CHAR_BIT * (N - S - 1)) | ... );
};
if constexpr (N == 1) {
    return do_it(std::uint8_t{}, std::make_index_sequence&lt;N&gt;{});
} else if constexpr (N == 2) {
    return do_it(std::uint16_t{}, std::make_index_sequence&lt;N&gt;{});
} else if constexpr (N &lt;= 4) {
    return do_it(std::uint32_t{}, std::make_index_sequence&lt;N&gt;{});
} else if constexpr (N &lt;= 8) {
    return do_it(std::uint64_t{}, std::make_index_sequence&lt;N&gt;{});
}


Even better, permit the compiler to do the math at its preferred bit-width (which is 64 bits on all the desktop platforms I care about) and then truncate it at the end. This produces similar codegen and reads nicer yet:
std::uint64_t result = [&amp;]&lt;std::size_t... S&gt;( std::index_sequence&lt;S...&gt;) {
    return ((static_cast&lt;std::uint64_t&gt;(bytes) &lt;&lt; CHAR_BIT * (N - S - 1)) | ... );
}(std::make_index_sequence&lt;N&gt;{});
if constexpr (N == 1) {
    return std::uint8_t(result);
} else if constexpr (N == 2) {
    return std::uint16_t(result);
} else if constexpr (N &lt;= 4) {
    return std::uint32_t(result);
} else {
    return std::uint64_t(result);
}


You have another bug when N == 1 (besides the off-by-one bug). When N == 1, the fold-expression has no | operations at all, and so it's just a uint8_t shifted by zero. That shift expression has type int. Which is not an unsigned integral type. So your return-type-constraint fails!
This is just another reason to do all the math first in uint64_t, and then cast down to uint8_t right before you return, as shown in my last example above.
Writing any test cases would have caught both this bug and the off-by-one bug. Test cases are always important! Especially when you're planning to put the code up for public review. (Or for review by coworkers, for that matter.)

Finally, I recommend parentheses to clarify the precedence of x &lt;&lt; CHAR_BIT * y. In context it's obvious what you expected the precedence to be; but as a reader, I'm not sure you're right. Put the parentheses so that I don't have to think about it even for a second.
However, in this context, that's a very minor point, because you clearly aren't expecting anybody to read the expression ((static_cast&lt;result&gt;(bytes) &lt;&lt; CHAR_BIT * (N - S - 1)) | ... ). It's a &quot;trust me&quot; line of code.
It's also silly to pretend that CHAR_BIT is relevant here. This code explodes spectacularly if CHAR_BIT is anything other than 8. So just write 8; and if you are compelled to work in a reference to CHAR_BIT, do so by writing
static_assert(CHAR_BIT == 8);

at the top of the function.