If you really must do this, here's one way that it could be done. Major caveat: I haven't tried this out, but I believe that it could work. Also note that I'm writing code in a gmail window... compilation as-is is not a goal; consider this a sketch of what it would look like (and I've probably got the syntax wrong in a bunch of places). It's possible that there are modes of operation of Ceres where it would actually be worse than the naïve approach (does Ceres ever interleave evaluations at different points in parameter space?)
Note that this compromises several abstraction barriers somewhat; such are the demands of performance. I'm assuming that the dimensionality of X is known at compile time. What it does is to hash the input values of x, and if that hash is the same as the last time through, it re-uses pre-calculated values of f. (Hash collisions are assumed to never happen.) The trick is to do this in an autodifferentiation context, so that x might be an array of doubles or of Jets. I define a little templated helper that gets the scalar value of either type. I'm only hashing the scalar component of the Jets - that works fine for the case that Ceres is invoking the cost function directly (at that point, the partials stored in the Jet aren't interesting, as they're always the same: the i'th partial of x[i] is one; everything else is zero) but if you ever invoke this from a context where the Jets contain the result of some previous computation (so their partials have structure) you might want to hash the partials as well. There's some locking because Ceres can call multiple cost functions in parallel.
template<typename T> double getValue(T v);
template<> double getValue<double>(double v) { return v; }
template<typename T, int N> getValue<Jet<T, N>>(Jet<T, N> v) { return v.a; }
struct MyCostFunctor() {
template<typename T>
bool operator()(const T* x, const T* y, T* residual) {
static std::array<T, DIMENSIONALITY_OF_X> cache_of_f;
static int64 hash_of_x;
static Mutex m;
ThingThatCanHashDoubles hasher;
for (int i = 0; i < DIMENSIONALITY_OF_X; i++) {
hasher.AddThingToHash(getValue(x[i]));
}
int64 hash = hasher.HashAllTheThings();
std::array<T, DIMENSIONALITY_OF_X> f;
{
MutexLock lock(&m);
if (hash != hash_of_x) {
for (int i = 0; i < DIMENSIONALITY_OF_X) {
cache_of_f[i] = f[i] = f(x[i]);
}
hash = hash_of_x;
} else {
for (int i = 0; i < DIMENSIONALITY_OF_X) {
f[i] = cache_of_f[i];
}
}
}
for (int j = 0; j < DIMENSIONALITY_OF_Y; j++) {
residuals[j] = g(f[i], y[j]);
}
}
};