In addition to the other answers you have, there are a couple of other cases where
foo/* could be different (not just files beginning with
.). Besides, if you wanted to have files that start with
. matched by your glob, you can enable that too with
shopt -s dotglob.
The first is if there are no entries in
foo/ and you have not enabled
foo/* will be returned as a literal passed to
cp. Since (in this case) there isn't a source
cp would complain, while
cp -R foo/ would always have at least
foo/ to copy. If you did enable
foo/* would expand to nothing, so you'd end up missing an argument to
Another case to consider is if there are a LOT of entries in
foo/. The shell expands the glob and then invokes the process, but if the glob expands to too many arguments you'll get an error. With
cp -R foo/ you only have 2 arguments (though presumably you'll have a target somewhere else).