Lazy generators: template deduction on the left-hand side
If you are constructing or assigning to a variable from some function template call, the template magic usually occurs on the right-hand side of the expression. For example:
But what if we could get
parse to deduce the type we want to parse from the left-hand side of the construction? I.e. what if we could write this:
This might not seem possible at first glance, since template deduction for
parse can only occur based on the function arguments. However, we can achieve this usage with a technique I call lazy generators.
parse doing all of the parsing work, it’s going to return a
parser, which is the lazy generator for our example.
Now the actual parsing work will be done in the implicit conversion template function of
parser can implicitly convert to any type with the relevant stream overloads and a default constructor, and this conversion will trigger the parsing.
You can also pass the parser as an argument to functions which expect parsable types (although be careful if doing so multiple times in a function call due to unspecified evaluation order):
If you wanted, you could add some
std::enable_if tricks to limit the types which your generator converts to:
Of course, as with any technique which relies on implicit conversion, this comes with a ton of caveats and gotchas. For one, you can do some very strange things by saving the parser object and passing it around:
Or you can copy the return value of
Or default-construct a
To prevent the above, you could restrict
parser to only be constructible by
parse, only allow implicit conversion of rvalue
parsers, and delete all of its copy and move operations.
Particularly deviant users can still take a reference to it with
auto&& p = parse() and implicitly convert using
std::move(p), but they deserve whatever befalls them as a result.
Perhaps you think that this trick is more trouble than its worth for a small example like the above. Mostly I just thought it was a cool trick to have in one’s arsenal, but here are some real-world examples of templated conversion operators, some of which also use lazy generators.
boost::nfp::named_parameteruses this trick to trace misuses of invalid parameters.
boost::python::overrideuses it to implicitly convert objects returned from Python.
boost::detail::winapi::detail(quite the mouthful) uses it to allow implicit casting of pointer types for communication with the Windows SDK.
boost::initialized_valueuses the templated implicit conversion to provide a generic value initialization method to work around various compiler issues.
boost::spirit::hold_anyallows implicit conversion to any type rather than explicit casting in some configurations.
boost::multiprecision::numbermarks the operator
explicitto only allow explicit casting.
I’m sure you could find many more uses for this technique for generic utilities, API adaption, embedded DSLs, etc.
Let me know what you think of this article on twitter @TartanLlama or leave a comment below!