From c85b20b46dcf9a73268a9d7b8b7646b4d917e450 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 6 Jan 2020 20:30:47 -0800 Subject: [PATCH] Add back port of std::span (#21) * Start filling out span backport * Fix assertion * Add a span test. * Fix constructors * Fill out span tests * Add test coverage for pointer-count construction * s/ASSERT_DEATH/EXPECT_DEATH/ * Add bracket operator test * Add test coverage for other span construction. * Add test coverage for array construction * Add test for range construction * Fix typo. * Add assignment test * Add iteration test * Add data/size functions * Add array overloads * Support implicit construction from general containers * Reformat * Fix typo * Fix typo * Fix enable_if condition --- api/include/opentelemetry/nostd/span.h | 240 ++++++++++++++++++++++ api/include/opentelemetry/nostd/utility.h | 56 +++++ api/test/nostd/BUILD | 22 ++ api/test/nostd/CMakeLists.txt | 10 + api/test/nostd/span_test.cc | 175 ++++++++++++++++ api/test/nostd/utility_test.cc | 41 ++++ 6 files changed, 544 insertions(+) create mode 100644 api/include/opentelemetry/nostd/span.h create mode 100644 api/include/opentelemetry/nostd/utility.h create mode 100644 api/test/nostd/span_test.cc create mode 100644 api/test/nostd/utility_test.cc diff --git a/api/include/opentelemetry/nostd/span.h b/api/include/opentelemetry/nostd/span.h new file mode 100644 index 00000000000..1e7f620380a --- /dev/null +++ b/api/include/opentelemetry/nostd/span.h @@ -0,0 +1,240 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/nostd/utility.h" + +namespace opentelemetry +{ +namespace nostd +{ +constexpr size_t dynamic_extent = static_cast(-1); + +template +class span; + +namespace detail +{ +/** + * Helper class to resolve overloaded constructors + */ +template +struct is_specialized_span_convertible : std::false_type +{}; + +template +struct is_specialized_span_convertible> : std::true_type +{}; + +template +struct is_specialized_span_convertible : std::true_type +{}; + +template +struct is_specialized_span_convertible> : std::true_type +{}; +} // namespace detail + +/** + * Back port of std::span. + * + * See https://en.cppreference.com/w/cpp/container/span for interface documentation. + * + * Note: This provides a subset of the methods available on std::span. + * + * Note: The std::span API specifies error cases to have undefined behavior, so this implementation + * chooses to terminate or assert rather than throw exceptions. + */ +template +class span +{ +public: + static constexpr size_t extent = Extent; + + // This arcane code is how we make default-construction result in an SFINAE error + // with C++11 when Extent != 0 as specified by the std::span API. + // + // See https://stackoverflow.com/a/10309720/4447365 + template ::type * = nullptr> + span() noexcept : data_{nullptr} + {} + + span(T *data, size_t count) noexcept : data_{data} + { + if (count != Extent) + { + std::terminate(); + } + } + + span(T *first, T *last) noexcept : data_{first} + { + if (std::distance(first, last) != Extent) + { + std::terminate(); + } + } + + template ::type * = nullptr> + span(T (&array)[N]) noexcept : data_{array} + {} + + template ::type * = nullptr> + span(std::array &array) noexcept : data_{array.data()} + {} + + template ::type * = nullptr> + span(const std::array &array) noexcept : data_{array.data()} + {} + + template < + class C, + typename std::enable_if::value && + std::is_convertible()))>::type (*)[], + T (*)[]>::value && + std::is_convertible())), + size_t>::value>::type * = nullptr> + span(C &c) noexcept(noexcept(nostd::data(c), nostd::size(c))) : data_{nostd::data(c)} + { + if (nostd::size(c) != Extent) + { + std::terminate(); + } + } + + template < + class C, + typename std::enable_if::value && + std::is_convertible()))>::type (*)[], + T (*)[]>::value && + std::is_convertible())), + size_t>::value>::type * = nullptr> + span(const C &c) noexcept(noexcept(nostd::data(c), nostd::size(c))) : data_{nostd::data(c)} + { + if (nostd::size(c) != Extent) + { + std::terminate(); + } + } + + template ::value>::type * = nullptr> + span(const span &other) noexcept : data_{other.data()} + {} + + span(const span &) noexcept = default; + + bool empty() const noexcept { return Extent == 0; } + + T *data() const noexcept { return data_; } + + size_t size() const noexcept { return Extent; } + + T &operator[](size_t index) const noexcept + { + assert(index < Extent); + return data_[index]; + } + + T *begin() const noexcept { return data_; } + + T *end() const noexcept { return data_ + Extent; } + +private: + T *data_; +}; + +template +class span +{ +public: + static constexpr size_t extent = dynamic_extent; + + span() noexcept : extent_{0}, data_{nullptr} {} + + span(T *data, size_t count) noexcept : extent_{count}, data_{data} {} + + span(T *first, T *last) noexcept + : extent_{static_cast(std::distance(first, last))}, data_{first} + { + assert(first <= last); + } + + template + span(T (&array)[N]) noexcept : extent_{N}, data_{array} + {} + + template + span(std::array &array) noexcept : extent_{N}, data_{array.data()} + {} + + template + span(const std::array &array) noexcept : extent_{N}, data_{array.data()} + {} + + template < + class C, + typename std::enable_if::value && + std::is_convertible()))>::type (*)[], + T (*)[]>::value && + std::is_convertible())), + size_t>::value>::type * = nullptr> + span(C &c) noexcept(noexcept(nostd::data(c), nostd::size(c))) + : extent_{nostd::size(c)}, data_{nostd::data(c)} + {} + + template < + class C, + typename std::enable_if::value && + std::is_convertible()))>::type (*)[], + T (*)[]>::value && + std::is_convertible())), + size_t>::value>::type * = nullptr> + span(const C &c) noexcept(noexcept(nostd::data(c), nostd::size(c))) + : extent_{nostd::size(c)}, data_{nostd::data(c)} + {} + + template ::value>::type * = nullptr> + span(const span &other) noexcept : extent_{other.size()}, data_{other.data()} + {} + + span(const span &) noexcept = default; + + bool empty() const noexcept { return extent_ == 0; } + + T *data() const noexcept { return data_; } + + size_t size() const noexcept { return extent_; } + + T &operator[](size_t index) const noexcept + { + assert(index < extent_); + return data_[index]; + } + + T *begin() const noexcept { return data_; } + + T *end() const noexcept { return data_ + extent_; } + +private: + // Note: matches libstdc++'s layout for std::span + // See + // https://github.com/gcc-mirror/gcc/blob/a60701e05b3878000ff9fdde1aecbc472b9dec5a/libstdc%2B%2B-v3/include/std/span#L402-L403 + size_t extent_; + T *data_; +}; +} // namespace nostd +} // namespace opentelemetry diff --git a/api/include/opentelemetry/nostd/utility.h b/api/include/opentelemetry/nostd/utility.h new file mode 100644 index 00000000000..f4780bfe1fd --- /dev/null +++ b/api/include/opentelemetry/nostd/utility.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +namespace opentelemetry +{ +namespace nostd +{ +/** + * Back port of std::data + * + * See https://en.cppreference.com/w/cpp/iterator/data + */ +template +auto data(C &c) noexcept(noexcept(c.data())) -> decltype(c.data()) +{ + return c.data(); +} + +template +auto data(const C &c) noexcept(noexcept(c.data())) -> decltype(c.data()) +{ + return c.data(); +} + +template +T *data(T (&array)[N]) noexcept +{ + return array; +} + +template +const E *data(std::initializer_list list) noexcept +{ + return list.begin(); +} + +/** + * Back port of std::size + * + * See https://en.cppreference.com/w/cpp/iterator/size + */ +template +auto size(const C &c) noexcept(noexcept(c.size())) -> decltype(c.size()) +{ + return c.size(); +} + +template +size_t size(T (&array)[N]) noexcept +{ + return N; +} +} // namespace nostd +} // namespace opentelemetry diff --git a/api/test/nostd/BUILD b/api/test/nostd/BUILD index fffd03468b4..bccc840f273 100644 --- a/api/test/nostd/BUILD +++ b/api/test/nostd/BUILD @@ -8,3 +8,25 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "utility_test", + srcs = [ + "utility_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "span_test", + srcs = [ + "span_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/api/test/nostd/CMakeLists.txt b/api/test/nostd/CMakeLists.txt index 946ed705e72..1f433ca0971 100644 --- a/api/test/nostd/CMakeLists.txt +++ b/api/test/nostd/CMakeLists.txt @@ -5,3 +5,13 @@ target_link_libraries(string_view_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) gtest_add_tests(TARGET string_view_test TEST_PREFIX nostd. TEST_LIST string_view_test) + +add_executable(utility_test utility_test.cc) +target_link_libraries(utility_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) +gtest_add_tests(TARGET utility_test TEST_PREFIX nostd. TEST_LIST utility_test) + +add_executable(span_test span_test.cc) +target_link_libraries(span_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) +gtest_add_tests(TARGET span_test TEST_PREFIX nostd. TEST_LIST span_test) diff --git a/api/test/nostd/span_test.cc b/api/test/nostd/span_test.cc new file mode 100644 index 00000000000..44e7d49a15d --- /dev/null +++ b/api/test/nostd/span_test.cc @@ -0,0 +1,175 @@ +#include "opentelemetry/nostd/span.h" + +#include +#include +#include +#include +#include +#include + +#include + +using opentelemetry::nostd::span; + +TEST(SpanTest, DefaultConstruction) +{ + span s1; + EXPECT_EQ(s1.data(), nullptr); + EXPECT_EQ(s1.size(), 0); + EXPECT_TRUE(s1.empty()); + + span s2; + EXPECT_EQ(s2.data(), nullptr); + EXPECT_EQ(s2.size(), 0); + EXPECT_TRUE(s2.empty()); + + EXPECT_FALSE((std::is_default_constructible>::value)); +} + +TEST(SpanTest, Assignment) +{ + std::array array1 = {1, 2, 3}; + std::array array2 = {1, 2, 3}; + span s1{array1.data(), array1.size()}; + span s2{array1.data(), array1.size()}; + + span s3; + s3 = s1; + EXPECT_EQ(s3.data(), array1.data()); + EXPECT_EQ(s3.size(), array1.size()); + + span s4{array2}; + s4 = s2; + EXPECT_EQ(s4.data(), array1.data()); + EXPECT_EQ(s4.size(), array1.size()); +} + +TEST(SpanTest, PointerCountConstruction) +{ + std::array array = {1, 2, 3}; + + span s1{array.data(), array.size()}; + EXPECT_EQ(s1.data(), array.data()); + EXPECT_EQ(s1.size(), array.size()); + + span s2{array.data(), array.size()}; + EXPECT_EQ(s2.data(), array.data()); + EXPECT_EQ(s2.size(), array.size()); + + EXPECT_DEATH((span{array.data(), array.size()}), ".*"); +} + +TEST(SpanTest, RangeConstruction) +{ + int array[] = {1, 2, 3}; + + span s1{std::begin(array), std::end(array)}; + EXPECT_EQ(s1.data(), array); + EXPECT_EQ(s1.size(), 3); + + span s2{std::begin(array), std::end(array)}; + EXPECT_EQ(s2.data(), array); + EXPECT_EQ(s2.size(), 3); + + EXPECT_DEATH((span{std::begin(array), std::end(array)}), ".*"); +} + +TEST(SpanTest, ArrayConstruction) +{ + int array1[] = {1, 2, 3}; + std::array array2 = {1, 2, 3}; + + span s1{array1}; + EXPECT_EQ(s1.data(), array1); + EXPECT_EQ(s1.size(), 3); + + span s2{array2}; + EXPECT_EQ(s2.data(), array2.data()); + EXPECT_EQ(s2.size(), array2.size()); + + span s3{array1}; + EXPECT_EQ(s3.data(), array1); + EXPECT_EQ(s3.size(), 3); + + span s4{array2}; + EXPECT_EQ(s4.data(), array2.data()); + EXPECT_EQ(s4.size(), array2.size()); + + EXPECT_FALSE((std::is_constructible, int(&)[3]>::value)); +} + +TEST(SpanTest, ContainerConstruction) +{ + std::vector v = {1, 2, 3}; + + span s1{v}; + EXPECT_EQ(s1.data(), v.data()); + EXPECT_EQ(s1.size(), v.size()); + + span s2{v}; + EXPECT_EQ(s2.data(), v.data()); + EXPECT_EQ(s2.size(), v.size()); + EXPECT_DEATH((span{v}), ".*"); + + EXPECT_FALSE((std::is_constructible, std::vector>::value)); + EXPECT_FALSE((std::is_constructible, std::list>::value)); +} + +TEST(SpanTest, OtherSpanConstruction) +{ + std::array array = {1, 2, 3}; + span s1{array.data(), array.size()}; + span s2{array.data(), array.size()}; + + span s3{s1}; + EXPECT_EQ(s3.data(), array.data()); + EXPECT_EQ(s3.size(), array.size()); + + span s4{s2}; + EXPECT_EQ(s4.data(), array.data()); + EXPECT_EQ(s4.size(), array.size()); + + span s5{s1}; + EXPECT_EQ(s5.data(), array.data()); + EXPECT_EQ(s5.size(), array.size()); + + EXPECT_FALSE((std::is_constructible, span>::value)); + EXPECT_FALSE((std::is_constructible, span>::value)); + + span s6{s2}; + EXPECT_EQ(s6.data(), array.data()); + EXPECT_EQ(s6.size(), array.size()); + + span s7{s2}; + EXPECT_EQ(s7.data(), array.data()); + EXPECT_EQ(s7.size(), array.size()); + + EXPECT_FALSE((std::is_constructible, span>::value)); + EXPECT_FALSE((std::is_constructible, span>::value)); +} + +TEST(SpanTest, BracketOperator) +{ + std::array array = {1, 2}; + + span s1{array.data(), array.size()}; + EXPECT_EQ(s1[0], 1); + EXPECT_EQ(s1[1], 2); + + span s2{array.data(), array.size()}; + EXPECT_EQ(s2[0], 1); + EXPECT_EQ(s2[1], 2); +} + +TEST(SpanTest, Iteration) +{ + std::array array = {1, 2, 3}; + + span s1{array.data(), array.size()}; + EXPECT_EQ(std::distance(s1.begin(), s1.end()), array.size()); + EXPECT_TRUE(std::equal(s1.begin(), s1.end(), array.begin())); + + span s2{array.data(), array.size()}; + EXPECT_EQ(std::distance(s2.begin(), s2.end()), array.size()); + EXPECT_TRUE(std::equal(s2.begin(), s2.end(), array.begin())); +} diff --git a/api/test/nostd/utility_test.cc b/api/test/nostd/utility_test.cc new file mode 100644 index 00000000000..ab477f40b88 --- /dev/null +++ b/api/test/nostd/utility_test.cc @@ -0,0 +1,41 @@ +#include "opentelemetry/nostd/utility.h" + +#include +#include + +#include + +template +auto IsDataCallable(const T &t) -> decltype(opentelemetry::nostd::data(t), std::true_type{}); + +std::false_type IsDataCallable(...); + +template +auto IsSizeCallable(const T &t) -> decltype(opentelemetry::nostd::size(t), std::true_type{}); + +std::false_type IsSizeCallable(...); + +TEST(UtilityTest, Data) +{ + std::vector v = {1, 2, 3}; + int array[3] = {1, 2, 3}; + std::initializer_list list{1, 2, 3}; + int x; + + EXPECT_EQ(opentelemetry::nostd::data(v), v.data()); + EXPECT_EQ(opentelemetry::nostd::data(array), array); + EXPECT_EQ(opentelemetry::nostd::data(list), list.begin()); + EXPECT_FALSE(decltype(IsDataCallable(x)){}); +} + +TEST(UtilityTest, Size) +{ + std::vector v = {1, 2, 3}; + int array[3] = {1, 2, 3}; + int x; + + EXPECT_EQ(opentelemetry::nostd::size(v), v.size()); + EXPECT_EQ(opentelemetry::nostd::size(array), 3); + + EXPECT_FALSE(decltype(IsSizeCallable(x)){}); +}