... or to a Table

To customize mappings within a table, pass a mapping_customization as an extra argument to the table's constructor. All the various forms of table and serial_table constructors allow this extra argument at the end. Thus:

serial_table<animal> animals(db, "animals", &animal::id, my_customization());

Now all the uint16_ts and uint32_ts in animals will be represented in signed integer columns of 16 and 32 bits respectively, as specified in my_customization. For any other polymorphically mapped type T, the choice of mapper class falls back to the database: it will apply its own custom mapper for T, if it has one, or else fall back to the defaults from the backend library.

Warning, Beware, Caution, Caveat

Table-level customizations are fine as long as you are storing values like this:

.where(animals->id == jumbo_id)
.update(animals->n_legs, uint16_t(4));

and retrieving them like this:

const optional<uint16_t> n_legs = animals
    .where(animals->id == jumbo_id)

but any use of table-level customized data in server-side expressions is fraught with hazards. E.g. in the expression animals->n_legs + 2u, the left operand is mapped according to the table's customized mapping, but the 2u (in that context) does not belong to the table, so it is mapped according to the database's mapping (which may or may not have its own customizations). And the expression as a whole does not belong to the table, so if it's value goes back to the client, it will be converted to C++ data by the database's mapping, not the table's.

My advice is: use table-level customization sparingly. It is for situations where you have a large amount of data that needs to be stored compactly, but then you need to keep that data away from server-side expressions.

All uses of numeric_cast_mapper and reinterpret_cast_mapper have their problems, as we know, but if the deployments stay at the database level, then at least you can be sure that a consistent set of mappings will apply across any given query.