TheIdeasMan wrote: |
---|
I found this for clang. |
Thank you. It seems to be exactly what I was looking for. I'm not normally using the llvm toolchain but I could probably get myself to run clang-tidy once in a while to look for these (and other) problems in my code.
mbozzi wrote: |
---|
It's not always wrong to move const things |
You mean like casting away the constness? I'm not sure I want to go down that direction. I'm afraid I that I might accidentally end up moving from something that is truly const.
mbozzi wrote: |
---|
You could write your own move function that fails when called with a const value. |
Yeah, that might actually be the best solutions because it requires no extra tools.
1 2 3 4 5 6
|
template <typename T>
decltype(auto) force_move(T&& value)
{
static_assert(!std::is_const_v<std::remove_reference_t<T>>, "Move can't be forced on const object.");
return std::move(value);
}
|
The only disadvantage is that if you do use a tool it might silence other warnings that the tool gives you. The page that TheIdeasMan linked to says that clang-tidy also warns about using std::move on trivially-copyable types and when the result is used as a const reference. Adding another static_assert to check for trivially-copyable types is easy but I don't think there is a way to guard against the return value being used incorrectly. This is not a big problem in my opinion but still something worth taking into consideration.
JLBorges wrote: |
---|
Want to move from an object that would otherwise have been logically const?
I suspect that this situation would be extremely rare in real life code; |
My main concern is with local variables. Some people, like Jason Turner (
https://youtu.be/uzF4u9KgUWI?t=7m48s), recommend using const essentially everywhere. I'm not so used to using const for local variables (except for compile-time constants) so maybe I'm overinterpreting what they are saying and use const in too many places.
Let me give an example. Say that I want to load objects of type X from files and store them in a vector. To construct an X object I need to pass it a name and a filepath. I want to use the filename as the name and since I no longer will be needing the filename I can just as well move it, so I end up with the following code:
1 2 3 4 5 6 7 8
|
const std::vector<std::string> filenames = dir.files();
std::vector<X> xs;
for (const std::string& filename : filenames)
{
const std::string filepath = dir.str() + '/' + filename;
xs.emplace_back(std::move(filename), filepath);
}
|
The problem here is that the filename does not get moved because it is const. I would probably have realized this when adding std::move so the code would probably have looked more like this:
1 2 3 4 5 6 7 8
|
std::vector<std::string> filenames = dir.files();
std::vector<X> xs;
for (std::string& filename : filenames)
{
const std::string filepath = dir.str() + '/' + filename;
xs.emplace_back(std::move(filename), filepath);
}
|
But now I have the concern that I described earlier. It is not immediately obvious why filenames and filename are not const so I might accidentally come back and "correct" the code later. If I had used auto& instead of std::string& for the filename the risk is even higher because all it takes is that I change the first line.
Am I doing something wrong here or am I just careless if I add const without thinking long and hard about the consequences first? Before we had move semantics it used to be safe to just throw const at everything. If it didn't work you just got an error message telling you where the problem was so that you could make a decision about whether or not const was appropriate right away.