2010-03-11

The most natural of all keys

I sometimes hear, as an argument for using surrogate keys, that there is
no natural key for a table of persons. There is. It's a compound key
consisting of your mother's primary key and a sequence number telling
which of her chldren you are. In SQL:

create table person
(
parent integer[],
sequence int,
name varchar(100),
primary key (parent, sequence)
);

insert into person (parent, sequence, name) values ({}, 0, 'Lucy');
insert into person (parent, sequence, name) values ({0}, 0, 'Albert');
insert into person (parent, sequence, name) values ({0}, 1, 'Betty');
insert into person (parent, sequence, name) values ({1,0}, 0, 'Zim');


Here, Lucy is the first human ever (she has no human ancestor). Albert
and Betty are her children, and Betty has a son, Zim.

The only problem with this is that the foreign key cannot be
described in SQL. It actually has nothing to do with it being a
self-referencing key, but that the parent and
sequence columns of the referenced row should be
parts of the parent column of the referencing row. Something
like the following: foreign key (parent[1], parent[2:]) references person
(sequence, parent)
. Unfortunatly, only column names are permitted
in foreign key constranits, not arbitrary expressions. Implementing
the constraint as a CHECK instead is left as an excercise to
the reader.

2010-03-09

Const correctnes and callback interfaces in C

From time to time, I use and/or design callback interfaces in C. Usually, the signature of the callback will be similar to

typedef int (*callback)(int some_argument, void* user_data);


and the function that calls the callback is

int do_something_with_callback(int another_argument, callback f, void* user_data);


This is all well and good (as long as the documentation for do_something_with_callback specifies exactly how if will call f). But consider the following example


#include <stdio.h>
int add(int a, void* user_data)
{
int* b=(int*)user_data;
return a + (*b);
}

typedef int (*callback)(int some_argument, void* user_data);

int apply_arithmetic_function(int initial_value, callback f, void* user_data)
{
return f(initial_value, user_data);
}

int main()
{
int v=1000;
printf("%d\n", apply_arithmetic_function(123, add, &v));
return 0;
}

This works, but as you can see, the add function does not write though its user_data pointer. Therefore, in isolation, it could be marked const, which is ususally a good thing. But is int add(int a, const void* user_data) compatible with the callback interface?

At first glance, it would seem like it, since something that takes a const pointer can be passed a non-const pointer (and gcc 4.3.4 with -Wall -Wextra -Werror -std=c99 -pedantic permitts it). But then again: what if the calling convention is different for const and non-const pointers? In a plain function call, it will work, since the compiler sees the signature of the actual function that is being called, but though the function pointer, it does not.

Looking at the (draft) spec, it is not permitted.
6.7.5.3§15 says:

For two function types to be compatible, both shall specify compatible return types. [127]

Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types.

127) If both function types are ‘‘old style’’, parameter types are not compared.


So, all the parameters in the signatures have to be "compatible". What does that mean? 6.7.3§9 says:

For two qualified types to be compatible, both shall have the identically qualified version of a compatible type ...

Ergo, the function signatures are not compatible, and 6.5.2.2§9 says:

If the function [that is to be called] is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.

Conclusion: be careful with the types of callback functions. Copy-paste verbatim from the declaration, and write a comment at the site of the callback implementations saying that they are supposed to conform to a specific interface and that the parameter declarations may not be modified unless the interface is also changed.