c++: avoid initializer_list<string> [PR105838]
When constructing a vector<string> from { "strings" }, first is built an initializer_list<string>, which is then copied into the strings in the vector. But this is inefficient: better would be treat the { "strings" } as a range and construct the strings in the vector directly from the string-literals. We can do this transformation for standard library classes because we know the design patterns they follow. PR c++/105838 gcc/cp/ChangeLog: * call.cc (list_ctor_element_type): New. (braced_init_element_type): New. (has_non_trivial_temporaries): New. (maybe_init_list_as_array): New. (maybe_init_list_as_range): New. (build_user_type_conversion_1): Use maybe_init_list_as_range. * parser.cc (cp_parser_braced_list): Call recompute_constructor_flags. * cp-tree.h (find_temps_r): Declare. gcc/testsuite/ChangeLog: * g++.dg/tree-ssa/initlist-opt1.C: New test.
This commit is contained in:
parent
1e1847612d
commit
d081807d8d
4 changed files with 165 additions and 0 deletions
138
gcc/cp/call.cc
138
gcc/cp/call.cc
|
@ -4154,6 +4154,134 @@ add_list_candidates (tree fns, tree first_arg,
|
|||
access_path, flags, candidates, complain);
|
||||
}
|
||||
|
||||
/* Given C(std::initializer_list<A>), return A. */
|
||||
|
||||
static tree
|
||||
list_ctor_element_type (tree fn)
|
||||
{
|
||||
gcc_checking_assert (is_list_ctor (fn));
|
||||
|
||||
tree parm = FUNCTION_FIRST_USER_PARMTYPE (fn);
|
||||
parm = non_reference (TREE_VALUE (parm));
|
||||
return TREE_VEC_ELT (CLASSTYPE_TI_ARGS (parm), 0);
|
||||
}
|
||||
|
||||
/* If EXPR is a braced-init-list where the elements all decay to the same type,
|
||||
return that type. */
|
||||
|
||||
static tree
|
||||
braced_init_element_type (tree expr)
|
||||
{
|
||||
if (TREE_CODE (expr) == CONSTRUCTOR
|
||||
&& TREE_CODE (TREE_TYPE (expr)) == ARRAY_TYPE)
|
||||
return TREE_TYPE (TREE_TYPE (expr));
|
||||
if (!BRACE_ENCLOSED_INITIALIZER_P (expr))
|
||||
return NULL_TREE;
|
||||
|
||||
tree elttype = NULL_TREE;
|
||||
for (constructor_elt &e: CONSTRUCTOR_ELTS (expr))
|
||||
{
|
||||
tree type = TREE_TYPE (e.value);
|
||||
type = type_decays_to (type);
|
||||
if (!elttype)
|
||||
elttype = type;
|
||||
else if (!same_type_p (type, elttype))
|
||||
return NULL_TREE;
|
||||
}
|
||||
return elttype;
|
||||
}
|
||||
|
||||
/* True iff EXPR contains any temporaries with non-trivial destruction.
|
||||
|
||||
??? Also ignore classes with non-trivial but no-op destruction other than
|
||||
std::allocator? */
|
||||
|
||||
static bool
|
||||
has_non_trivial_temporaries (tree expr)
|
||||
{
|
||||
auto_vec<tree*> temps;
|
||||
cp_walk_tree_without_duplicates (&expr, find_temps_r, &temps);
|
||||
for (tree *p : temps)
|
||||
{
|
||||
tree t = TREE_TYPE (*p);
|
||||
if (!TYPE_HAS_TRIVIAL_DESTRUCTOR (t)
|
||||
&& !is_std_allocator (t))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We're initializing an array of ELTTYPE from INIT. If it seems useful,
|
||||
return INIT as an array (of its own type) so the caller can initialize the
|
||||
target array in a loop. */
|
||||
|
||||
static tree
|
||||
maybe_init_list_as_array (tree elttype, tree init)
|
||||
{
|
||||
/* Only do this if the array can go in rodata but not once converted. */
|
||||
if (!CLASS_TYPE_P (elttype))
|
||||
return NULL_TREE;
|
||||
tree init_elttype = braced_init_element_type (init);
|
||||
if (!init_elttype || !SCALAR_TYPE_P (init_elttype) || !TREE_CONSTANT (init))
|
||||
return NULL_TREE;
|
||||
|
||||
tree first = CONSTRUCTOR_ELT (init, 0)->value;
|
||||
if (TREE_CODE (init_elttype) == INTEGER_TYPE && null_ptr_cst_p (first))
|
||||
/* Avoid confusion from treating 0 as a null pointer constant. */
|
||||
first = build1 (UNARY_PLUS_EXPR, init_elttype, first);
|
||||
first = (perform_implicit_conversion_flags
|
||||
(elttype, first, tf_none, LOOKUP_IMPLICIT|LOOKUP_NO_NARROWING));
|
||||
if (first == error_mark_node)
|
||||
/* Let the normal code give the error. */
|
||||
return NULL_TREE;
|
||||
|
||||
/* Don't do this if the conversion would be constant. */
|
||||
first = maybe_constant_init (first);
|
||||
if (TREE_CONSTANT (first))
|
||||
return NULL_TREE;
|
||||
|
||||
/* We can't do this if the conversion creates temporaries that need
|
||||
to live until the whole array is initialized. */
|
||||
if (has_non_trivial_temporaries (first))
|
||||
return NULL_TREE;
|
||||
|
||||
init_elttype = cp_build_qualified_type (init_elttype, TYPE_QUAL_CONST);
|
||||
tree arr = build_array_of_n_type (init_elttype, CONSTRUCTOR_NELTS (init));
|
||||
return finish_compound_literal (arr, init, tf_none);
|
||||
}
|
||||
|
||||
/* If we were going to call e.g. vector(initializer_list<string>) starting
|
||||
with a list of string-literals (which is inefficient, see PR105838),
|
||||
instead build an array of const char* and pass it to the range constructor.
|
||||
But only do this for standard library types, where we can assume the
|
||||
transformation makes sense.
|
||||
|
||||
Really the container classes should have initializer_list<U> constructors to
|
||||
get the same effect more simply; this is working around that lack. */
|
||||
|
||||
static tree
|
||||
maybe_init_list_as_range (tree fn, tree expr)
|
||||
{
|
||||
if (BRACE_ENCLOSED_INITIALIZER_P (expr)
|
||||
&& is_list_ctor (fn)
|
||||
&& decl_in_std_namespace_p (fn))
|
||||
{
|
||||
tree to = list_ctor_element_type (fn);
|
||||
if (tree init = maybe_init_list_as_array (to, expr))
|
||||
{
|
||||
tree begin = decay_conversion (TARGET_EXPR_SLOT (init), tf_none);
|
||||
tree nelts = array_type_nelts_top (TREE_TYPE (init));
|
||||
tree end = cp_build_binary_op (input_location, PLUS_EXPR, begin,
|
||||
nelts, tf_none);
|
||||
begin = cp_build_compound_expr (init, begin, tf_none);
|
||||
return build_constructor_va (init_list_type_node, 2,
|
||||
NULL_TREE, begin, NULL_TREE, end);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
/* Returns the best overload candidate to perform the requested
|
||||
conversion. This function is used for three the overloading situations
|
||||
described in [over.match.copy], [over.match.conv], and [over.match.ref].
|
||||
|
@ -4425,6 +4553,16 @@ build_user_type_conversion_1 (tree totype, tree expr, int flags,
|
|||
return cand;
|
||||
}
|
||||
|
||||
/* Maybe pass { } as iterators instead of an initializer_list. */
|
||||
if (tree iters = maybe_init_list_as_range (cand->fn, expr))
|
||||
if (z_candidate *cand2
|
||||
= build_user_type_conversion_1 (totype, iters, flags, tf_none))
|
||||
if (cand2->viable == 1)
|
||||
{
|
||||
cand = cand2;
|
||||
expr = iters;
|
||||
}
|
||||
|
||||
tree convtype;
|
||||
if (!DECL_CONSTRUCTOR_P (cand->fn))
|
||||
convtype = non_reference (TREE_TYPE (TREE_TYPE (cand->fn)));
|
||||
|
|
|
@ -7087,6 +7087,7 @@ extern void set_global_friend (tree);
|
|||
extern bool is_global_friend (tree);
|
||||
|
||||
/* in init.cc */
|
||||
extern tree find_temps_r (tree *, int *, void *);
|
||||
extern tree expand_member_init (tree);
|
||||
extern void emit_mem_initializers (tree);
|
||||
extern tree build_aggr_init (tree, tree, int,
|
||||
|
|
|
@ -25445,6 +25445,7 @@ cp_parser_braced_list (cp_parser* parser, bool* non_constant_p)
|
|||
location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
|
||||
braces.require_close (parser);
|
||||
TREE_TYPE (initializer) = init_list_type_node;
|
||||
recompute_constructor_flags (initializer);
|
||||
|
||||
cp_expr result (initializer);
|
||||
/* Build a location of the form:
|
||||
|
|
25
gcc/testsuite/g++.dg/tree-ssa/initlist-opt1.C
Normal file
25
gcc/testsuite/g++.dg/tree-ssa/initlist-opt1.C
Normal file
|
@ -0,0 +1,25 @@
|
|||
// PR c++/105838
|
||||
// { dg-additional-options -fdump-tree-gimple }
|
||||
// { dg-do compile { target c++11 } }
|
||||
|
||||
// Test that we do range-initialization from const char *.
|
||||
// { dg-final { scan-tree-dump {_M_range_initialize<const char\* const\*>} "gimple" } }
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
void g (const void *);
|
||||
|
||||
void f (const char *p)
|
||||
{
|
||||
std::vector<std::string> lst = {
|
||||
"aahing", "aaliis", "aarrgh", "abacas", "abacus", "abakas", "abamps", "abands", "abased", "abaser", "abases", "abasia",
|
||||
"abated", "abater", "abates", "abatis", "abator", "abattu", "abayas", "abbacy", "abbess", "abbeys", "abbots", "abcees",
|
||||
"abdabs", "abduce", "abduct", "abears", "abeigh", "abeles", "abelia", "abends", "abhors", "abided", "abider", "abides",
|
||||
"abject", "abjure", "ablate", "ablaut", "ablaze", "ablest", "ablets", "abling", "ablins", "abloom", "ablush", "abmhos",
|
||||
"aboard", "aboded", "abodes", "abohms", "abolla", "abomas", "aboral", "abords", "aborne", "aborts", "abound", "abouts",
|
||||
"aboves", "abrade", "abraid", "abrash", "abrays", "abrazo", "abrege", "abrins", "abroad", "abrupt", "abseil", "absent",
|
||||
};
|
||||
|
||||
g(&lst);
|
||||
}
|
Loading…
Add table
Reference in a new issue