Daniel Schemmel
I was recently playing around with Perl 6
Some languages supply similar concepts explicitly. Personally, I know this concept from Racket
#lang racket
(define parameter (make-parameter "defaulted"))
(define (parameterized #:parameter (param (parameter)))
; well, this was easy
(displayln param))
; usage:
(parameterized)
(parameterize ([parameter "value1"])
(parameterized)
(parameterize ([parameter "value2"])
(parameterized)
(parameterized #:parameter "value3"))
(parameterized))
(parameterized)
This code snippet combines three important features:
defaulted
) that is used in absence of any user-defined parameters.parameterized
invocation.So, how would this look in Perl 6? Even simpler, as it turns out:
sub parameterized(:$parameter = $*parameter // "defaulted") {
# well, this was easy
say $parameter;
}
# usage:
parameterized();
{
parameterized();
my $*parameter ::= "value1";
parameterized();
{
my $*parameter ::= "value2";
parameterized();
parameterized(:parameter("value3"));
}
parameterized();
}
parameterized();
The code has the exact same semantics as the one used in the Racket version. Note that $*parameter
is not even lexically defined on the outmost scope!
At this point it should be noted how important it is for either of the previous implementations that the default argument is evaluated every time the function is called.
I tend to spout commonalities like "C++ can do anything all the other languages do, it just might be less convenient!". Sooo… Time to put my money where my mouth is!
The basic idea is rather simple again: Use a thread_local
variable that contains the current parameterization and constructors and destructors to change it to and fro. This way the parameter stays changed for the lifetime of an object, and is reverted afterwards1. I have to use an optional positional argument instead of using a keyword argument, as keyword arguments are not natively supported in C++ (of course there always is Boost
Naturally, while it is possible to build it, it is significantly more complicated to do so than it was in either Perl 6 or Racket:
#include <vector>
#include <iostream>
void parameterized(char const*);
class parameter_t {
friend void parameterized(char const*);
static thread_local char const* parameter;
char const* old_parameter;
public:
parameter_t(char const* str) : old_parameter(parameter)
{ parameter = str; }
~parameter_t() { parameter = old_parameter; }
};
thread_local char const* (::parameter_t::parameter) = "defaulted";
void parameterized(char const* str = ::parameter_t::parameter) {
// Finally, the actual function!
::std::cout << str << "\n";
}
// usage:
// parameter_t custom_default("custom default"); // this works!
int main() {
parameterized();
{
parameterized();
parameter_t _("value1");
parameterized();
{
parameter_t _("value2");
parameterized();
parameterized("value3");
}
parameterized();
}
parameterized();
}
So, where does one actually use such a construct? What purpose does it serve beyond simple entertainment of creating it?
My favorite example is that of functions that create output. I have lost track of how often I had to pass ::std::cout
explicitly2, instead of just specifying it as the output stream once. While it is possible3 to change the standard output, it is very non-generic, requiring additional efforts on the behalf of the programmer.
Another common case in which this might be interesting is for programs that are built as a big library with a small driver – like for example Clang. Since the library should not have state with static storage duration, unless absolutely necessary, program options have to be chained through all functions until they are needed.
This is (only) correct due to the fact that destructors are called in reverse order than constructors, allowing us to just store a single variable in the object instead of having to build our own stack, or similar constructs. ↩
Don't forget that ::std::ostream& operator<<(::std::ostream&, some_type const&);
is a function as well. ↩
E.g. by changing the streambuf
buffer