Readability matters.
/a/ && /b/ && /c/ && /d/ && /e/
This is possibly a little bit slower than some of the solutions below. But speed isn't everything, especially if both alternatives are quite fast.
Readability and maintainability are usually far more important, and this is clearly far more readable and maintainable than the original.
Still, I'll continue with other alternatives.
First of all, we can micro-optimize what you have:
/^(?=.*a)(?=.*b)(?=.*c)(?=.*d).*e/s
This eliminates a serious amount of backtracking when the pattern doesn't match, and it probably even speeds up positive matches by not checking for line feeds.
Even faster,
/^(?=.*?a)(?=.*?b)(?=.*?c)(?=.*?d).*?e/s
For strings to match literally, though, using index instead of the regex engine should be faster.
my @strs = qw( a b c d e );
my $str = $_;
@strs == grep { index($str, $_) >= 0 } @strs
use List::Util qw( all );
my @strs = qw( a b c d e );
my $str = $_;
all { index($str, $_) >= 0 } @strs
An approach that works well for a dynamic list of characters:
my @chars = qw( a b c d e );
my %counts;
++$counts{$_} for unpack '(a)*', $_;
@chars == grep { $counts{$_} } @chars
use List::Util qw( all );
my @chars = qw( a b c d e );
my %counts;
++$counts{$_} for unpack '(a)*', $_;
all { $counts{$_} } @chars
The downside is that this doesn't short circuit. It still needs to process the entire string even if it starts with abcde.
If you have a dynamic list of patterns, you can still use some of the earlier approaches.
# /^(?=.*?a)(?=.*?b)(?=.*?c)(?=.*?d).*?e/s
my $pat = join "", map { "(?=.*?$_)" } @pats;
my $re = qr/^$pat/s;
/$re/
# /a/ && /b/ && /c/ && /d/ && /e/
my $str = $_;
@pats == grep { $str =~ /$_/ } @pats
# /a/ && /b/ && /c/ && /d/ && /e/
use List::Util qw( all );
my $str = $_;
all { $str =~ /$_/ } @pats