There are two problems here.
First, you don't actually call the lambda, so instead of passing the returned value into test, you pass the function object, which is clearly a completely incompatible type! Solve this by adding () after the lambda to call it, thereby passing the return value to test().
[](){ return 42; } ();
// ^^ now the whole expression has value 42
Secondly, you are right, the deduced return type will be B, not B&, and the temporary object may not be bound to the ref-to-non-const argument of test(B&).
One way around this would be to use a trailing return type to force a reference:
[&a]() -> B& { .... }
You seem to know this, but don't want to do it. Why?
Another option is to return a reference wrapper, which is then returned by value but behaves like a reference:
return std::ref(a.getB()));
Another option would be to change test to be able to accept some temporary. Since you need this to be able to modify the original object, you could have test take a pointer, or other type with reference semantics (a smart pointer, a std::reference_wrapper<B>, make B have reference semantics when you copy it, ...)
void test(B* b) {
++(b->i);
}
...
test([&]() { return &a.getB(); } () );
Note for pedants: I know the parens in ++(b->i) aren't strictly necessary. I find it clearer.