I'm using a Cvar base class which contains the code for managing a bag of observers (CvarHandler objects) and provides an interface to the console for getting and setting the value. A std::map is used by the console to map Cvar objects to their names. Values are provided as strings, and boost::lexical_cast can be used to convert to the actual data type (and at the same time verify that the user input is good).
It's similar for Commands. A Command base class provides the interface that actual command objects must provide, but there's no listeners. Again, strings are used for input (this time, a vector of them) and actual determination of input is done by the Command object itself. Another std::map in the console keeps Commands.
When the user gives input to the console, the console first checks if the first token in the input string matches a command name. If not, it'll check for a cvar name, and if that also fails, tells the user that he's an idiot. But because of the possibility of a cvar and a command having the same name, the console provides two special commands, get and set, for ensuring that cvars can be accessed and mutated. (There are two other special commands, listvars and listcmds, for providing the names and descriptions (and for cvars, values) of registered cvars and commands.)
So far I've not been having problems working with this system, but so far I've only built it. I need to write a driver and unit tests to ensure that it works as planned.