Is there a way to join WP_Query without a filter?

No. There isn’t. The only way to add custom joins to a WordPress query is through the posts_join filter or posts_clauses filter, which contains all the parts of the query.

Those filters receive the WP_Query object as a parameter though, which allows you to create custom arguments for WP_Query that will affect the query.

function wpse_303758_posts_join( $join, $query ) {
    if ( $query->get( 'custom_arg' ) === true ) {
        $join .= " LEFT JOIN my_table ON $wpdb->posts.ID = my_table.post_id";
    }

    return $join;
}

With that filter the JOIN will be added to the query if custom_arg is true in WP_Query:

$query = new WP_Query( array(
    'custom_arg' => true,
) );