The select(2), poll(2), and epoll(7) system calls are just a way to wait for events on file descriptors (small integers representing an I/O channel). Events can include "data read for read", "ready to write", things like that. Your code makes up a set of file descriptors that will eventually have an event, and then calls select(), or poll() or epoll() to cause the program to wait until data arrives, the kernel connects the socket to some other host, a descriptor had an error, whatever.
signalfd(2) adds a new event: a signal has arrived. In unix/linux/*BSD a "signal" is a more-or-less asynchronous event: CPU tried to execute an illegal instruction, I/O is ready, code divided by zero, the modem hung up. signalfd(2) lets you create a file descriptor, usable in select(), poll(), epoll(), that has an event when a signal arrives.
In the past, you would specify a handler function, which the kernel magically called when a signal arrives (a.k.a. an "upcall"). If you used the sigaction() system call to tell the kernel what function you wanted it to call, you got the same information that sigwaitinfo() can get you.
The difference between setting a handler function with signal() or sigaction() over signalfd() is that the handler function could get magically called at any time: parts of your code had to be re-entrant (not just thread-safe, mind you) to deal with the "at any time" nature of signals. With signalfd(), a signal is just another event to deal with in your code's event loop. Your code just executes as usual.
sigwaitinfo() causes your code to pause until and when a signal you specified arrives. Your code does nothing until that signal arrives. No event loop, no nothing. It also looks like sigwaitinfo() is part of the real time stuff in the Linux kernel. sigwaitinfo() could be thought of as designating a spot in the code for the kernel to call when a signal arrives, rather than designating a function to call.
I've just discovered a blog post sbout the "self pipe trick". Apparently not only is it inconvenient code-wise to handle signals and select-based I/O, you can also suffer "nasty race conditions". The self pipe trick gets around that by doing essentially what signalfd(2) does, but all in user space, and at a cost of more code.