The conditional operator only evaluates what's necessary, but you have a precedence problem.
First of all, the conditional operator is indeed guaranteed to use short-circuit evaluation, meaning it only evaluates what's necessary.
$ perl -M5.010 -e'
sub f { say "f" }
sub g { say "g" }
$ARGV[0] ? f() : g();
' 0
f
$ perl -M5.010 -e'
sub f { say "f" }
sub g { say "g" }
say $ARGV[0] ? f() : g();
' 1
g
This is even true for E1 || E2, E1 or E2, E1 && E2 and E1 and E2. They only evaluate their right-hand side operand if necessary.
$ perl -M5.010 -e'
sub f { say "f"; $ARGV[0] }
sub g { say "g"; $ARGV[1] }
say f() || g();
' 3 4
f
3
$ perl -M5.010 -e'
sub f { say "f"; $ARGV[0] }
sub g { say "g"; $ARGV[1] }
say f() || g();
' 0 4
f
g
4
This is why you can can evaluate open(...) or die(...) safely. Without short-circuiting, it would evaluate die whether open was successful or not.
Now, let's explain the following:
$s = "X"; 1 ? $s .= "_" : $s = "_"; say $s; # _
It's a precedence problem. The above is equivalent to
$s = "X"; ( 1 ? ($s .= "_") : $s ) = "_"; say $s; # _
$s .= "_" returns $s, so the conditional operator returns $s, so the string _ is assigned to $s. If we add parens to get the desired parsing, we get the expected result.
$s = "X"; 1 ? ($s .= "_") : ($s = "_"); say $s; # X_
Alternative:
$s = "X"; $s = ( 1 ? $s : "" ) . "_"; say $s; # X_