This issue is caught and flagged by clang-tidy. Though technically the standard says it is in valid but un-specified state. Because of short string optimisation, the move might be possibly a copy, so one should not rely on the contents being empty.
Clang tidy covers only the most trivial use after move scenarios. It's useful coverage but isn't and never will be complete like the rust borrow checker.
No, you can absolutely use it (but you most likely want to clear() or assign an empty string to it before doing e.g. push_back as move does not necessarily clear the moved-from object - std::move(some_int) won't clear the int)
Also, uninitialized by itself does not mean invalid though ? For your example, I wonder if it would make sense to flag it: printf could be implemented in assembly or Fortran for what we know (a few popular libc implementations are done in c++ for instance), and as such I don't know how much sense it would make for its internal usage of the pointed value to be checked against the c++ rules. I'd assume the outcome would be different with std::format or std::cout for instance
My complaint is that it is too easy to create an object with uninitialized members by accident. On account of the syntax being identical. I guess its too late to add affordances there.
I suppose a definition of valid that prohibited uninitialized members would preclude lots of useful stuff like container and buffer types.
From the msan documentation [1], the flaw with my earlier example is that `printf` isn't instrumented.
And to address your other question, I don't know if msan can instrument a function implemented in assembly. It definitely can't deal with something it didn't compile as the instrumentation is added during compilation.
It seems that on godbolt the platform library also isn't instrumented because the equivalent iostream code is msan clean [2]. I suppose that makes sense as it's allowing you to pass arbitrary options to the compiler.
In summary, msan can detect these uninitialized reads but it requires quite a lot of fiddling.
std::string str1{"Apple"}; std::string str2 = std::move(str1);
Isn't str1 in an invalid (err, unspecified?) state and you shouldn't use it? It's not `null` sure, but it's not good to use.