Google Blogger has implemented a new set of Lambda expression operators for its template language. See: https://blogger.googleblog.com/2016/05/more-custom-template-flexibility.h
The solution to my problem
Stupid of me, if I expect to be able to use a loop I need to specify a loop:
<b:loop values='data:posts filter (p => p.id != 0)' var='post'>
<h1>Post found: <data:post.title/></h1>
</b:loop>
How to use Blogger Lambda Operators
For those coming here trying to find help I recommend the following article which explains how to use Lambda expressions quite comprehensively: http://www.bloggerever.com/2016/05/what-are-exactly-bloggers-lambda.html and is based on https://productforums.google.com/forum/#!topic/blogger/l3phi8bscGY.
In essence there are 7 Lambda operators
Each of which can be used either inside an if or loop conditional statement. Some untested sample code follows to help get you started.
The any operator returns true if any items in the Lambda return true, so for example, the following code would return true if a post had a label labela or labelb associated with it:
<b:if cond='data:post.labels any
(l => l.name in {"labela","labelb"})'>
...Code here...
</b:if>
all would return true if it had both labela and labelb associated with it:
<b:if cond='data:post.labels all
(l => l.name not in {"labela","labelb"})'>
...Code here...
</b:if>
none would return true if it had neither labela and labelb associated with it:
<b:if cond='data:post.labels all
(l => l.name not in {"labela","labelb"})'>
...Code here...
</b:if>
count would return 0, 1 or 2 depending on whether a post had neither labela or labelb associated with it, or just labela or both labela and labelb
<b:if cond='data:post.labels count
(l => l.name not in {"labela","labelb"})'>
...Code here...
</b:if>
filter will return an array and requires a loop. The example below would print out the title of each post unless it had an id of 0 (which i don't is possible!)
<b:loop values='data:posts filter (p => p.id != 0)' var='post'>
<h1>Post found: <data:post.title/></h1>
</b:loop>
first like filter, but will return the first match only.
<b:loop values='data:posts first(p => p.timestamp == "4.2.09")' var='post'>
<h1>Post found: <data:post.title/></h1>
</b:loop>
map returns an array set containing each result from the Lambda. The code below would display labels in a h1 tag with >> prepended.
<b:loop values='data:post.labels map (l=> ">>" + l.name)' var='label'>
<h1><data:label/></h1>
</b:loop>
A final note: the article linked to suggests the Lambda's may only be applied to post, label and comment elements. It certainly works with the first two but I haven't tried it with comments.