Contrary to "only one obvious way to do it," because UTF-8 encoded codepoints are a maximum of four bytes long, you can manually unroll the loop into a maximum of four turns for a slight boost. (If you're lucky or clever, you might even be able to check all four bytes for being a starting byte at the same time!)
I know I've implemented this exact functionality (aligning an arbitrary byte index to a character boundary), but a quick look wasn't able to find it again.
But to be completely honest, in the supermajority of cases, I think you don't want to do this. (Thus a reason to let it be outside std: those that need it can get it but it's not going to tempt people that shouldn't.)
Specifically, because using an arbitrary byte index into a UTF-8 string that isn't on a codepoint border is pretty broken. How did you get the index in the first place? If it's supposed to be a codepoint border, you definitely want to know if it isn't, which is difficult to do so with a provided align function.
Basically, I think the overwhelming majority of cases you might legitimately want this functionality are when implementing Unicode algorithms, in which case you definitely are pulling in a Unicode library that can do that or implementing that library.