Changed make_list to take its arguments by forwarding reference (#1528)

make_list was the only make_xxx container factory taking its argument
pack by value rather than by forwarding reference, costing an extra
move per lvalue argument: each lvalue was copy-constructed into the
parameter and then moved into the initializer_list backing array,
where the forwarding-reference signature copies it there directly.
Rvalue prvalue arguments count the same either way in practice, as
copy elision constructs them directly into the by-value parameter.

Change the pack to T&&..., matching make_vector, make_forward_list and
make_indirect_vector. The deduced reference types decay through
common_type, so the element type deduction is unchanged.

Adds tests for const/non-const lvalues of the element type (guarding
against the forward<T> defect fixed for make_array and make_deque),
mixed-type common_type deduction, and an instrumented type counting
copy/move constructions, which fails with the by-value signature (one
extra move per lvalue argument) and passes with the fix.

Also corrects the make_list documentation, which described a
make_array-style signature with an explicit element type parameter
that never matched the implementation.

Verified with clang 22 (C++17): all 93 list tests pass; C++14/20
syntax checks pass.

Note: mixed-type lvalue arguments are affected by a separate
pre-existing defect in etl::common_type (its two-type specialization
recurses with decay<T2> twice instead of decay<T1>, decay<T2>), which
already affects the other common_type-based factories and is being
fixed separately.

Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>
This commit is contained in:
rleigh-electradx 2026-08-04 10:57:25 +01:00 committed by GitHub
parent 9d92081d1e
commit 62925ec1ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 111 additions and 4 deletions

View File

@ -69,13 +69,15 @@ Defines data as an list of `int`, of length `10`, containing the supplied data.
## Make template
**C++11 and above**
```cpp
template <typename T, typename... TValues>
constexpr auto make_list(TValues&&... values)
template <typename... T>
constexpr auto make_list(T&&... t) -> etl::list<typename etl::common_type_t<T...>, sizeof...(T)>
```
The element type is deduced as the common type of the arguments.
### Example
```cpp
auto data = etl::make_list<int>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
auto data = etl::make_list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
```
## Member types

View File

@ -2219,7 +2219,7 @@ namespace etl
//*************************************************************************
#if ETL_USING_CPP11 && ETL_HAS_INITIALIZER_LIST
template <typename... T>
constexpr auto make_list(T... t) -> etl::list<typename etl::common_type_t<T...>, sizeof...(T)>
constexpr auto make_list(T&&... t) -> etl::list<typename etl::common_type_t<T...>, sizeof...(T)>
{
return {etl::forward<T>(t)...};
}

View File

@ -2272,5 +2272,110 @@ namespace
CHECK_EQUAL(ItemNDC("F"), *itr++);
}
#endif
//*************************************************************************
#if ETL_HAS_INITIALIZER_LIST
// Const-qualified lvalue arguments must be accepted and must not affect the
// deduced element type (the deduced reference types decay via common_type).
TEST(test_make_list_from_const_lvalues_of_element_type)
{
static const char static_const_lvalue = 42;
const char local_const_lvalue = 43;
char mutable_lvalue = 44;
auto data = etl::make_list(static_const_lvalue, local_const_lvalue, mutable_lvalue);
CHECK((std::is_same<etl::list<char, 3U>, decltype(data)>::value));
auto itr = data.begin();
CHECK_EQUAL(42, *itr++);
CHECK_EQUAL(43, *itr++);
CHECK_EQUAL(44, *itr++);
}
#endif
//*************************************************************************
#if ETL_HAS_INITIALIZER_LIST
TEST(test_make_list_common_type_from_mixed_types)
{
auto data = etl::make_list(1, 2L, 3);
CHECK((std::is_same<etl::list<long, 3U>, decltype(data)>::value));
auto itr = data.begin();
CHECK_EQUAL(1L, *itr++);
CHECK_EQUAL(2L, *itr++);
CHECK_EQUAL(3L, *itr++);
}
#endif
//*************************************************************************
#if ETL_HAS_INITIALIZER_LIST
struct CopyMoveCounted
{
explicit CopyMoveCounted(int value_)
: value(value_)
{
}
CopyMoveCounted(const CopyMoveCounted& other)
: value(other.value)
{
++copy_count;
}
CopyMoveCounted(CopyMoveCounted&& other)
: value(other.value)
{
++move_count;
}
int value;
static size_t copy_count;
static size_t move_count;
};
size_t CopyMoveCounted::copy_count = 0;
size_t CopyMoveCounted::move_count = 0;
// The arguments must be forwarded into the initializer list, not taken by
// value: a by-value parameter pack costs an extra move per argument.
// Each element is then copied from the initializer_list backing array into
// its list node, which is why lvalue arguments count two copies.
TEST(test_make_list_argument_copy_and_move_counts)
{
const CopyMoveCounted const_lvalue(1);
CopyMoveCounted mutable_lvalue(2);
CopyMoveCounted::copy_count = 0;
CopyMoveCounted::move_count = 0;
auto data = etl::make_list(const_lvalue, mutable_lvalue);
CHECK_EQUAL(4U, CopyMoveCounted::copy_count);
#if ETL_USING_CPP17
// The exact move counts rely on guaranteed copy elision. Before C++17 the
// returned list may be move-constructed once more, which moves each element,
// so only the copy counts are checked there.
CHECK_EQUAL(0U, CopyMoveCounted::move_count);
#endif
CopyMoveCounted::copy_count = 0;
CopyMoveCounted::move_count = 0;
auto data2 = etl::make_list(CopyMoveCounted(3));
CHECK_EQUAL(1U, CopyMoveCounted::copy_count);
#if ETL_USING_CPP17
CHECK_EQUAL(1U, CopyMoveCounted::move_count);
#endif
auto itr = data.begin();
CHECK_EQUAL(1, (*itr++).value);
CHECK_EQUAL(2, (*itr).value);
CHECK_EQUAL(3, data2.begin()->value);
}
#endif
}
} // namespace