diff --git a/doc/compat/reference.adoc b/doc/compat/reference.adoc index 2fb457a..114d916 100644 --- a/doc/compat/reference.adoc +++ b/doc/compat/reference.adoc @@ -12,4 +12,5 @@ include::invoke.adoc[] include::latch.adoc[] include::mem_fn.adoc[] include::shared_lock.adoc[] +include::to_array.adoc[] include::type_traits.adoc[] diff --git a/doc/compat/to_array.adoc b/doc/compat/to_array.adoc new file mode 100644 index 0000000..02ee883 --- /dev/null +++ b/doc/compat/to_array.adoc @@ -0,0 +1,72 @@ +//// +Copyright 2024 Ruben Perez Hidalgo +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#to_array] +# +:idprefix: ref_to_array_ + +## Description + +The header `` implements, in a portable way, the C++20 +`std::to_array` function, present in the `` header. + +`to_array` creates a `std::array` from a single-dimensional C array, +performing an element-wise copy or move. + +## Example + +```cpp +int input [] = {1, 2, 3}; +std::array output = boost::compat::to_array(input); +assert(( + output == std::array{{1, 2, 3}} +)); +``` + +## Synopsis + +```cpp +namespace boost +{ +namespace compat +{ + +template +constexpr std::array, N> to_array(T (&a)[N]); + +template +constexpr std::array, N> to_array(T (&&a)[N]); + +} // namespace compat +} // namespace boost +``` + +## to_array + +```cpp +template +constexpr std::array, N> to_array(T (&&a)[N]); +``` + +[horizontal] +Effects:;; Creates an array of `N` elements by copying elements in `a`. + For every `i` in `0, ..., N-1`, copy-initializes the i-th element + in the output array from `a[i]`. +Type requirements:;; `std::is_constructible_v, T&> && !std::is_array_v`. + Otherwise, the overload is ill-formed. + + +```cpp +template +constexpr std::array, N> to_array(T (&a)[N]); +``` + +[horizontal] +Effects:;; Creates an array of `N` elements by moving elements in `a`. + For every `i` in `0, ..., N-1`, move-initializes the i-th element + in the output array from `std::move(a[i])`. +Type requirements:;; `std::is_constructible_v, T&&> && !std::is_array_v`. + Otherwise, the overload is ill-formed. diff --git a/include/boost/compat/to_array.hpp b/include/boost/compat/to_array.hpp new file mode 100644 index 0000000..53190d6 --- /dev/null +++ b/include/boost/compat/to_array.hpp @@ -0,0 +1,60 @@ +#ifndef BOOST_COMPAT_TO_ARRAY_HPP_INCLUDED +#define BOOST_COMPAT_TO_ARRAY_HPP_INCLUDED + +// Copyright 2024 Ruben Perez Hidalgo +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include +#include +#include +#include + +namespace boost { +namespace compat { + +namespace detail { + +template +constexpr std::array, N> to_array_lvalue(T (&a)[N], index_sequence) +{ + return {{a[I]...}}; +} + +template +constexpr std::array, N> to_array_rvalue(T (&&a)[N], index_sequence) +{ + return {{std::move(a[I])...}}; +} + +} // namespace detail + +template +constexpr std::array, N> to_array(T (&a)[N]) +{ + static_assert( + std::is_constructible, T&>::value, + "This overload requires the resulting element type to be constructible from T&" + ); + static_assert(!std::is_array::value, "to_array does not work for multi-dimensional C arrays"); + return detail::to_array_lvalue(a, make_index_sequence{}); +} + +template +constexpr std::array, N> to_array(T (&&a)[N]) +{ + static_assert( + std::is_constructible, T&&>::value, + "This overload requires the resulting element type to be constructible from T&&" + ); + static_assert(!std::is_array::value, "to_array does not work for multi-dimensional C arrays"); + return detail::to_array_rvalue(static_cast(a), make_index_sequence{}); +} + +} // namespace compat +} // namespace boost + +#endif // BOOST_COMPAT_TO_ARRAY_HPP_INCLUDED diff --git a/test/Jamfile b/test/Jamfile index 33c1a69..66c9d6a 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -112,3 +112,8 @@ run function_ref_mfn_test.cpp ; run function_ref_fn_noexcept_test.cpp ; run function_ref_mfn_noexcept_test.cpp ; run function_ref_obj_noexcept_test.cpp ; + +# MSVC 14.0/14.1 C array rvalue references do not work +run to_array_lvalue_test.cpp ; +run to_array_rvalue_test.cpp : : : + msvc-14.0:no msvc-14.1:no ; diff --git a/test/to_array_lvalue_test.cpp b/test/to_array_lvalue_test.cpp new file mode 100644 index 0000000..8d4d821 --- /dev/null +++ b/test/to_array_lvalue_test.cpp @@ -0,0 +1,60 @@ +// Copyright 2024 Ruben Perez Hidalgo. +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include +#include + +namespace compat = boost::compat; + +int main() +{ + { + // regular C array + int input[] = {1, 2}; + auto output = compat::to_array(input); + static_assert(std::is_same>::value, ""); + BOOST_TEST_EQ(output[0], 1); + BOOST_TEST_EQ(output[1], 2); + } + { + // regular C array, const + const int input[] = {1, 2}; + auto output = compat::to_array(input); + static_assert(std::is_same>::value, ""); + BOOST_TEST_EQ(output[0], 1); + BOOST_TEST_EQ(output[1], 2); + } + { + // values not moved + const std::vector vec{1, 2}; + std::vector input[]{vec}; + auto output = compat::to_array(input); + static_assert(std::is_same, 1>>::value, ""); + BOOST_TEST_ALL_EQ(output[0].begin(), output[0].end(), vec.begin(), vec.end()); + BOOST_TEST_ALL_EQ(input[0].begin(), input[0].end(), vec.begin(), vec.end()); // input not modified + } + { + // const values work + const std::vector vec{1, 2}; + const std::vector input[]{vec}; + auto output = compat::to_array(input); + static_assert(std::is_same, 1>>::value, ""); + BOOST_TEST_ALL_EQ(output[0].begin(), output[0].end(), vec.begin(), vec.end()); + BOOST_TEST_ALL_EQ(input[0].begin(), input[0].end(), vec.begin(), vec.end()); // input not modified + } + { + // constexpr check + constexpr int input[] = {1, 2, 3}; + constexpr auto output = compat::to_array(input); + static_assert(std::is_same>::value, ""); + BOOST_TEST_EQ(output[0], 1); + BOOST_TEST_EQ(output[1], 2); + BOOST_TEST_EQ(output[2], 3); + } + + return boost::report_errors(); +} diff --git a/test/to_array_rvalue_test.cpp b/test/to_array_rvalue_test.cpp new file mode 100644 index 0000000..99960d2 --- /dev/null +++ b/test/to_array_rvalue_test.cpp @@ -0,0 +1,71 @@ +// Copyright 2024 Ruben Perez Hidalgo. +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include +#include +#include +#include + +namespace compat = boost::compat; + +int main() +{ + { + // regular C array + int input[] = {5, 6}; + auto output = compat::to_array(std::move(input)); + static_assert(std::is_same>::value, ""); + BOOST_TEST_EQ(output[0], 5); + BOOST_TEST_EQ(output[1], 6); + } + { + // regular C array, const + const int input[] = {5, 6}; + auto output = compat::to_array(std::move(input)); + static_assert(std::is_same>::value, ""); + BOOST_TEST_EQ(output[0], 5); + BOOST_TEST_EQ(output[1], 6); + } + { + // values moved + const std::vector vec{1, 2}; + std::vector input[]{vec}; + auto output = compat::to_array(std::move(input)); + static_assert(std::is_same, 1>>::value, ""); + BOOST_TEST_ALL_EQ(output[0].begin(), output[0].end(), vec.begin(), vec.end()); + BOOST_TEST(input[0].empty()); // input left in a moved-from state + } + { + // const values work (although they don't result in an effective move) + const std::vector vec{1, 2}; + const std::vector input[]{vec}; + auto output = compat::to_array(std::move(input)); + static_assert(std::is_same, 1>>::value, ""); + BOOST_TEST_ALL_EQ(output[0].begin(), output[0].end(), vec.begin(), vec.end()); + BOOST_TEST_ALL_EQ(input[0].begin(), input[0].end(), vec.begin(), vec.end()); // input not modified + } + { + // move-only types work + std::unique_ptr input[] = {std::unique_ptr{new int(42)}}; + int* ptr = input[0].get(); + auto output = compat::to_array(std::move(input)); + static_assert(std::is_same, 1>>::value, ""); + BOOST_TEST_EQ(output[0].get(), ptr); + BOOST_TEST_EQ(input[0].get(), nullptr); // input left in a moved-from state + } + { + // constexpr check + constexpr int input[] = {1, 2, 3}; + constexpr auto output = compat::to_array(std::move(input)); + static_assert(std::is_same>::value, ""); + BOOST_TEST_EQ(output[0], 1); + BOOST_TEST_EQ(output[1], 2); + BOOST_TEST_EQ(output[2], 3); + } + + return boost::report_errors(); +}