It is not easily possible, but it can be done. It requires either a trick or some Python programming.
The trick is old-school. What you do is make a breakpoint at the start of the function, then set a temporary breakpoint at the return location that does what you want. For example:
break function
commands
up-silently
tbreak
commands
printf "hi!\n"
cont
end
cont
end
One thing to note here is that tbreak without arguments will stop at the current PC -- which in the up frame is the return location.
This will work pretty well in many situations -- but not all. In particular, if you are using next to step through the code, and you step over a call to function (even one somewhere far down the stack), gdb will act as though you typed cont, and will forget to stop for the next. (Also I have a vague recollection that in some versions of gdb you can't nest commands like this. So there may be that to contend with as well.)
This next quirk, FWIW, is one of the things that dprintf was meant to deal with more nicely.
With Python scripting, you can do better -- you can make it work correctly with next as well. The way to do this is to subclass gdb.FinishBreakpoint, and, in the code above, create an instance of your subclass.
Your subclass can do all the actions you like. In your case, you would implement the stop method to print the message you want, and then return False. This is what will let the finish breakpoint not cause a user-visible stop, and thus make next work properly.
One other nice thing about FinishBreakpoint is that you have access to the function's return value.