Suppose I have a function that takes an argument of type T.
It does not mutate it, so I have the choice of passing it by const reference const T&
The most appropriate rule of thumb in my opinion is pass by reference when :
sizeof(T) >= sizeof(T*)
The idea behind this is that when you take by reference, at worst your compiler might implement this using a pointer.
This of course doesn't take into account the complexity of your copy constructor and move semantics and all the hell that can be created around your object life cycle.
Also if you don't care about micro optimisations you can pass everything by const reference, on most machines pointer are 4 or 8 bytes, very few types are smaller than that and even in that case you would lose a few (less than 8) bytes copy operation and some indirections which in modern world is most likely not gonna be your bottleneck :)