Dynamic Scoping in C++

Date: 2020-07-10

I always had a faible for dynamic scoping as it is implemented in Common Lisp, Emacs Lisp, or LaTeX. To me, it seems that dynamic scoping is an almost forgotten technique and dismissed technique in modern programming. While it is more complex to understand and more magically than lexical scoping, there are some applications that benefit from dynamic scoping.

In lexical scoping, if you access a variable, your compiler will start searching the declaration from the point of reference outwards in lexical order. This means it searches first in the most inner scope, and continoues to widen its search scope by the "closed-nested-scope rule". For example, in the following code snippet, the call to foo() would return 23, as the lexcially-closest declaration is the global variable.

int config = 23;

return foo() {
    return config;
}

void bar() {
    int config = 42;
    printf("%d\", foo());
}

With dynamic scoping, the world would look different as the closest (time-wise) dynamic binding for a given name is found instead of lexically closest. In the example, foo() would return 42, as the binding in bar() happened more recently that the global binding.

You can think of a dynamically-scoped global variable as shadowable global binding. By creating a new binding for the variable, we shadow the old binding, and all references in child functions will now refer to the new binding, although they have not received the value via an argument transfer. Ergo, dynamically-scoped variables are shadowable side-channels that can influence the behavior of a function.

For example, in Common Lisp, the variable *standard-output* is dynamically scoped and functions (print) simply send there characters to that variable. As it is dynamically scoped, we can just redirect all output to a file by creating a new binding for *standard-output*.

(with-open-file (*standard-output* "somefile.dat" :direction :output
                                   :if-exists :supersede)
   (print "this goes into file"))

Wouldn't it be neat to do the same in, let's say, C++? Luckily, with templates, the RAII pattern, and some template magic, we can write code like this:

DynamicScope<int> G(0);

void bar() {
    std::cout << "bar " << *G << std::endl;
}

void foo() {
    DynamicScope<int>::BindInstance _(G, 3);
    bar();
}
int main() {
    std::cout << "main1 " << *G << std::endl;
    foo();
    std::cout << "main2 " << *G << std::endl;

}

and end up with the output:

main1 0
bar 3
main2 0

The source for the DynamicScope<T> can be found here: dynamic_scope.cc