diff options
author | Abseil Team <absl-team@google.com> | 2020-06-10 15:35:34 -0700 |
---|---|---|
committer | vslashg <gfalcon@google.com> | 2020-06-10 18:59:53 -0400 |
commit | e7ebf9803746b9a115d96164bdf5e915be8f223b (patch) | |
tree | 2ccafec5290edd068ee47751efb56f2eb51134b2 | |
parent | 2eba343b51e0923cd3fb919a6abd6120590fc059 (diff) |
Export of internal Abseil changes
--
44b312da54263fc7491b5f1e115e49e0c1e2dc10 by Greg Falcon <gfalcon@google.com>:
Import of CCTZ from GitHub.
PiperOrigin-RevId: 315782632
--
27618a3b195f75384ba44e9712ae0b0b7d85937e by Greg Falcon <gfalcon@google.com>:
Update Abseil's internal Invoke() implementation to follow C++17 semantics.
Starting in C++17, when invoke'ing a pointer-to-member, if the object representing the class is a reference_wrapper, that wrapper is unpacked. Because we implement a number of functional APIs that closely match C++ standard proposals, it is better if we follow the standard's notion of what "invoking" means.
This also makes `absl::base_internal::Invoke()` match `std::invoke()` in C++17. I intend to make this an alias in a follow-up CL.
PiperOrigin-RevId: 315750659
--
059519ea402cd55b1b716403bb680504c6ff5808 by Xiaoyi Zhang <zhangxy@google.com>:
Internal change
PiperOrigin-RevId: 315597064
--
5e2042c8520576b2508e2bfb1020a97c7db591da by Titus Winters <titus@google.com>:
Update notes on the delta between absl::Span vs. std::span.
PiperOrigin-RevId: 315518942
--
9d3875527b93124f5de5d6a1d575c42199fbf323 by Abseil Team <absl-team@google.com>:
Internal cleanup
PiperOrigin-RevId: 315497633
GitOrigin-RevId: 44b312da54263fc7491b5f1e115e49e0c1e2dc10
Change-Id: I24573f317c8388bd693c0fdab395a7dd419b33b0
-rw-r--r-- | absl/base/internal/invoke.h | 131 | ||||
-rw-r--r-- | absl/base/invoke_test.cc | 34 | ||||
-rw-r--r-- | absl/functional/bind_front_test.cc | 19 | ||||
-rw-r--r-- | absl/status/status.cc | 2 | ||||
-rw-r--r-- | absl/strings/str_split.h | 1 | ||||
-rw-r--r-- | absl/time/internal/cctz/include/cctz/civil_time_detail.h | 42 | ||||
-rw-r--r-- | absl/time/internal/cctz/src/civil_time_test.cc | 10 | ||||
-rw-r--r-- | absl/types/span.h | 15 |
8 files changed, 194 insertions, 60 deletions
diff --git a/absl/base/internal/invoke.h b/absl/base/internal/invoke.h index c4eceebd..ab5ad910 100644 --- a/absl/base/internal/invoke.h +++ b/absl/base/internal/invoke.h @@ -18,16 +18,19 @@ // [func.require] // Define INVOKE (f, t1, t2, ..., tN) as follows: // 1. (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T -// and t1 is an object of type T or a reference to an object of type T or a -// reference to an object of a type derived from T; -// 2. ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a -// class T and t1 is not one of the types described in the previous item; -// 3. t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is -// an object of type T or a reference to an object of type T or a reference -// to an object of a type derived from T; -// 4. (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 -// is not one of the types described in the previous item; -// 5. f(t1, t2, ..., tN) in all other cases. +// and is_base_of_v<T, remove_reference_t<decltype(t1)>> is true; +// 2. (t1.get().*f)(t2, ..., tN) when f is a pointer to a member function of a +// class T and remove_cvref_t<decltype(t1)> is a specialization of +// reference_wrapper; +// 3. ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a +// class T and t1 does not satisfy the previous two items; +// 4. t1.*f when N == 1 and f is a pointer to data member of a class T and +// is_base_of_v<T, remove_reference_t<decltype(t1)>> is true; +// 5. t1.get().*f when N == 1 and f is a pointer to data member of a class T and +// remove_cvref_t<decltype(t1)> is a specialization of reference_wrapper; +// 6. (*t1).*f when N == 1 and f is a pointer to data member of a class T and t1 +// does not satisfy the previous two items; +// 7. f(t1, t2, ..., tN) in all other cases. // // The implementation is SFINAE-friendly: substitution failure within Invoke() // isn't an error. @@ -48,7 +51,16 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace base_internal { -// The five classes below each implement one of the clauses from the definition +template <typename T> +struct IsReferenceWrapper : std::false_type {}; +template <typename T> +struct IsReferenceWrapper<std::reference_wrapper<T>> : std::true_type {}; + +template <typename T> +using RemoveCvrefT = + typename std::remove_cv<typename std::remove_reference<T>::type>::type; + +// The seven classes below each implement one of the clauses from the definition // of INVOKE. The inner class template Accept<F, Args...> checks whether the // clause is applicable; static function template Invoke(f, args...) does the // invocation. @@ -72,9 +84,10 @@ struct MemFunAndRef : StrippedAccept<MemFunAndRef> { template <typename MemFunType, typename C, typename Obj, typename... Args> struct AcceptImpl<MemFunType C::*, Obj, Args...> - : std::integral_constant<bool, std::is_base_of<C, Obj>::value && - absl::is_function<MemFunType>::value> { - }; + : std::integral_constant< + bool, std::is_base_of< + C, typename std::remove_reference<Obj>::type>::value && + absl::is_function<MemFunType>::value> {}; template <typename MemFun, typename Obj, typename... Args> static decltype((std::declval<Obj>().* @@ -85,17 +98,41 @@ struct MemFunAndRef : StrippedAccept<MemFunAndRef> { } }; +// (t1.get().*f)(t2, ..., tN) when f is a pointer to a member function of a +// class T and remove_cvref_t<decltype(t1)> is a specialization of +// reference_wrapper; +struct MemFunAndRefWrap : StrippedAccept<MemFunAndRefWrap> { + template <typename... Args> + struct AcceptImpl : std::false_type {}; + + template <typename MemFunType, typename C, typename RefWrap, typename... Args> + struct AcceptImpl<MemFunType C::*, RefWrap, Args...> + : std::integral_constant< + bool, IsReferenceWrapper<RemoveCvrefT<RefWrap>>::value && + absl::is_function<MemFunType>::value> {}; + + template <typename MemFun, typename RefWrap, typename... Args> + static decltype((std::declval<RefWrap>().get().* + std::declval<MemFun>())(std::declval<Args>()...)) + Invoke(MemFun&& mem_fun, RefWrap&& ref_wrap, Args&&... args) { + return (std::forward<RefWrap>(ref_wrap).get().* + std::forward<MemFun>(mem_fun))(std::forward<Args>(args)...); + } +}; + // ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a -// class T and t1 is not one of the types described in the previous item. +// class T and t1 does not satisfy the previous two items; struct MemFunAndPtr : StrippedAccept<MemFunAndPtr> { template <typename... Args> struct AcceptImpl : std::false_type {}; template <typename MemFunType, typename C, typename Ptr, typename... Args> struct AcceptImpl<MemFunType C::*, Ptr, Args...> - : std::integral_constant<bool, !std::is_base_of<C, Ptr>::value && - absl::is_function<MemFunType>::value> { - }; + : std::integral_constant< + bool, !std::is_base_of< + C, typename std::remove_reference<Ptr>::type>::value && + !IsReferenceWrapper<RemoveCvrefT<Ptr>>::value && + absl::is_function<MemFunType>::value> {}; template <typename MemFun, typename Ptr, typename... Args> static decltype(((*std::declval<Ptr>()).* @@ -106,17 +143,18 @@ struct MemFunAndPtr : StrippedAccept<MemFunAndPtr> { } }; -// t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is -// an object of type T or a reference to an object of type T or a reference -// to an object of a type derived from T. +// t1.*f when N == 1 and f is a pointer to data member of a class T and +// is_base_of_v<T, remove_reference_t<decltype(t1)>> is true; struct DataMemAndRef : StrippedAccept<DataMemAndRef> { template <typename... Args> struct AcceptImpl : std::false_type {}; template <typename R, typename C, typename Obj> struct AcceptImpl<R C::*, Obj> - : std::integral_constant<bool, std::is_base_of<C, Obj>::value && - !absl::is_function<R>::value> {}; + : std::integral_constant< + bool, std::is_base_of< + C, typename std::remove_reference<Obj>::type>::value && + !absl::is_function<R>::value> {}; template <typename DataMem, typename Ref> static decltype(std::declval<Ref>().*std::declval<DataMem>()) Invoke( @@ -125,16 +163,39 @@ struct DataMemAndRef : StrippedAccept<DataMemAndRef> { } }; -// (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 -// is not one of the types described in the previous item. +// t1.get().*f when N == 1 and f is a pointer to data member of a class T and +// remove_cvref_t<decltype(t1)> is a specialization of reference_wrapper; +struct DataMemAndRefWrap : StrippedAccept<DataMemAndRefWrap> { + template <typename... Args> + struct AcceptImpl : std::false_type {}; + + template <typename R, typename C, typename RefWrap> + struct AcceptImpl<R C::*, RefWrap> + : std::integral_constant< + bool, IsReferenceWrapper<RemoveCvrefT<RefWrap>>::value && + !absl::is_function<R>::value> {}; + + template <typename DataMem, typename RefWrap> + static decltype(std::declval<RefWrap>().get().*std::declval<DataMem>()) + Invoke(DataMem&& data_mem, RefWrap&& ref_wrap) { + return std::forward<RefWrap>(ref_wrap).get().* + std::forward<DataMem>(data_mem); + } +}; + +// (*t1).*f when N == 1 and f is a pointer to data member of a class T and t1 +// does not satisfy the previous two items; struct DataMemAndPtr : StrippedAccept<DataMemAndPtr> { template <typename... Args> struct AcceptImpl : std::false_type {}; template <typename R, typename C, typename Ptr> struct AcceptImpl<R C::*, Ptr> - : std::integral_constant<bool, !std::is_base_of<C, Ptr>::value && - !absl::is_function<R>::value> {}; + : std::integral_constant< + bool, !std::is_base_of< + C, typename std::remove_reference<Ptr>::type>::value && + !IsReferenceWrapper<RemoveCvrefT<Ptr>>::value && + !absl::is_function<R>::value> {}; template <typename DataMem, typename Ptr> static decltype((*std::declval<Ptr>()).*std::declval<DataMem>()) Invoke( @@ -160,12 +221,18 @@ struct Invoker { typedef typename std::conditional< MemFunAndRef::Accept<Args...>::value, MemFunAndRef, typename std::conditional< - MemFunAndPtr::Accept<Args...>::value, MemFunAndPtr, + MemFunAndRefWrap::Accept<Args...>::value, MemFunAndRefWrap, typename std::conditional< - DataMemAndRef::Accept<Args...>::value, DataMemAndRef, - typename std::conditional<DataMemAndPtr::Accept<Args...>::value, - DataMemAndPtr, Callable>::type>::type>:: - type>::type type; + MemFunAndPtr::Accept<Args...>::value, MemFunAndPtr, + typename std::conditional< + DataMemAndRef::Accept<Args...>::value, DataMemAndRef, + typename std::conditional< + DataMemAndRefWrap::Accept<Args...>::value, + DataMemAndRefWrap, + typename std::conditional< + DataMemAndPtr::Accept<Args...>::value, DataMemAndPtr, + Callable>::type>::type>::type>::type>::type>::type + type; }; // The result type of Invoke<F, Args...>. diff --git a/absl/base/invoke_test.cc b/absl/base/invoke_test.cc index 6aa613c9..994d36b9 100644 --- a/absl/base/invoke_test.cc +++ b/absl/base/invoke_test.cc @@ -158,31 +158,56 @@ TEST(InvokeTest, MemberFunction) { std::unique_ptr<const Class> cp(new Class); std::unique_ptr<volatile Class> vp(new Class); + Class c; + std::reference_wrapper<Class> ref(c); + std::reference_wrapper<const Class> ref_const(c); + const std::reference_wrapper<Class> const_ref(c); + std::reference_wrapper<volatile Class> ref_volatile(c); + EXPECT_EQ(1, Invoke(&Class::Method, p, 3, 2)); EXPECT_EQ(1, Invoke(&Class::Method, p.get(), 3, 2)); EXPECT_EQ(1, Invoke(&Class::Method, *p, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::Method, ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::Method, const_ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::Method, std::move(ref), 3, 2)); EXPECT_EQ(1, Invoke(&Class::RefMethod, p, 3, 2)); EXPECT_EQ(1, Invoke(&Class::RefMethod, p.get(), 3, 2)); EXPECT_EQ(1, Invoke(&Class::RefMethod, *p, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::RefMethod, ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::RefMethod, const_ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::RefMethod, std::move(ref), 3, 2)); EXPECT_EQ(1, Invoke(&Class::RefRefMethod, std::move(*p), 3, 2)); // NOLINT EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, p, 3, 2)); EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, p.get(), 3, 2)); EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, *p, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, const_ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::NoExceptMethod, std::move(ref), 3, 2)); EXPECT_EQ(1, Invoke(&Class::ConstMethod, p, 3, 2)); EXPECT_EQ(1, Invoke(&Class::ConstMethod, p.get(), 3, 2)); EXPECT_EQ(1, Invoke(&Class::ConstMethod, *p, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::ConstMethod, ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::ConstMethod, const_ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::ConstMethod, std::move(ref), 3, 2)); EXPECT_EQ(1, Invoke(&Class::ConstMethod, cp, 3, 2)); EXPECT_EQ(1, Invoke(&Class::ConstMethod, cp.get(), 3, 2)); EXPECT_EQ(1, Invoke(&Class::ConstMethod, *cp, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::ConstMethod, ref_const, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::ConstMethod, std::move(ref_const), 3, 2)); EXPECT_EQ(1, Invoke(&Class::VolatileMethod, p, 3, 2)); EXPECT_EQ(1, Invoke(&Class::VolatileMethod, p.get(), 3, 2)); EXPECT_EQ(1, Invoke(&Class::VolatileMethod, *p, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::VolatileMethod, ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::VolatileMethod, const_ref, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::VolatileMethod, std::move(ref), 3, 2)); EXPECT_EQ(1, Invoke(&Class::VolatileMethod, vp, 3, 2)); EXPECT_EQ(1, Invoke(&Class::VolatileMethod, vp.get(), 3, 2)); EXPECT_EQ(1, Invoke(&Class::VolatileMethod, *vp, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::VolatileMethod, ref_volatile, 3, 2)); + EXPECT_EQ(1, Invoke(&Class::VolatileMethod, std::move(ref_volatile), 3, 2)); EXPECT_EQ(1, Invoke(&Class::Method, make_unique<Class>(), 3, 2)); EXPECT_EQ(1, Invoke(&Class::ConstMethod, make_unique<Class>(), 3, 2)); @@ -192,8 +217,15 @@ TEST(InvokeTest, MemberFunction) { TEST(InvokeTest, DataMember) { std::unique_ptr<Class> p(new Class{42}); std::unique_ptr<const Class> cp(new Class{42}); + Class c{42}; + std::reference_wrapper<Class> ref(c); + std::reference_wrapper<const Class> ref_const(c); + const std::reference_wrapper<Class> const_ref(c); EXPECT_EQ(42, Invoke(&Class::member, p)); EXPECT_EQ(42, Invoke(&Class::member, *p)); + EXPECT_EQ(42, Invoke(&Class::member, ref)); + EXPECT_EQ(42, Invoke(&Class::member, const_ref)); + EXPECT_EQ(42, Invoke(&Class::member, std::move(ref))); EXPECT_EQ(42, Invoke(&Class::member, p.get())); Invoke(&Class::member, p) = 42; @@ -201,6 +233,8 @@ TEST(InvokeTest, DataMember) { EXPECT_EQ(42, Invoke(&Class::member, cp)); EXPECT_EQ(42, Invoke(&Class::member, *cp)); + EXPECT_EQ(42, Invoke(&Class::member, ref_const)); + EXPECT_EQ(42, Invoke(&Class::member, std::move(ref_const))); EXPECT_EQ(42, Invoke(&Class::member, cp.get())); } diff --git a/absl/functional/bind_front_test.cc b/absl/functional/bind_front_test.cc index 4801a81c..92b6e8e5 100644 --- a/absl/functional/bind_front_test.cc +++ b/absl/functional/bind_front_test.cc @@ -228,4 +228,23 @@ TEST(BindTest, Mangling) { absl::bind_front(ManglingCall{}, 1, 3.3)("A"); } +struct Adder { + int add(int v2) const { return v + v2; } + int v; +}; + +TEST(BindTest, InvokeSemantics) { + Struct s1 = {"value"}; + auto f1 = absl::bind_front(&Struct::value); + EXPECT_EQ(f1(s1), "value"); + EXPECT_EQ(f1(&s1), "value"); + EXPECT_EQ(f1(std::ref(s1)), "value"); + + Adder add_100 = {100}; + auto f2 = absl::bind_front(&Adder::add); + EXPECT_EQ(f2(add_100, 23), 123); + EXPECT_EQ(f2(&add_100, 45), 145); + EXPECT_EQ(f2(std::ref(add_100), 67), 167); +} + } // namespace diff --git a/absl/status/status.cc b/absl/status/status.cc index 6d57a6be..0a655736 100644 --- a/absl/status/status.cc +++ b/absl/status/status.cc @@ -27,8 +27,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN -// The implementation was intentionally kept same as util::error::Code_Name() -// to ease the migration. std::string StatusCodeToString(StatusCode code) { switch (code) { case StatusCode::kOk: diff --git a/absl/strings/str_split.h b/absl/strings/str_split.h index a79cd4a0..1ce17f38 100644 --- a/absl/strings/str_split.h +++ b/absl/strings/str_split.h @@ -44,6 +44,7 @@ #include <vector> #include "absl/base/internal/raw_logging.h" +#include "absl/base/macros.h" #include "absl/strings/internal/str_split_internal.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" diff --git a/absl/time/internal/cctz/include/cctz/civil_time_detail.h b/absl/time/internal/cctz/include/cctz/civil_time_detail.h index 4cde96f1..d1b4222b 100644 --- a/absl/time/internal/cctz/include/cctz/civil_time_detail.h +++ b/absl/time/internal/cctz/include/cctz/civil_time_detail.h @@ -106,54 +106,64 @@ CONSTEXPR_F int days_per_month(year_t y, month_t m) noexcept { CONSTEXPR_F fields n_day(year_t y, month_t m, diff_t d, diff_t cd, hour_t hh, minute_t mm, second_t ss) noexcept { - y += (cd / 146097) * 400; + year_t ey = y % 400; + const year_t oey = ey; + ey += (cd / 146097) * 400; cd %= 146097; if (cd < 0) { - y -= 400; + ey -= 400; cd += 146097; } - y += (d / 146097) * 400; + ey += (d / 146097) * 400; d = d % 146097 + cd; if (d > 0) { if (d > 146097) { - y += 400; + ey += 400; d -= 146097; } } else { if (d > -365) { // We often hit the previous year when stepping a civil time backwards, // so special case it to avoid counting up by 100/4/1-year chunks. - y -= 1; - d += days_per_year(y, m); + ey -= 1; + d += days_per_year(ey, m); } else { - y -= 400; + ey -= 400; d += 146097; } } if (d > 365) { - for (int n = days_per_century(y, m); d > n; n = days_per_century(y, m)) { + for (;;) { + int n = days_per_century(ey, m); + if (d <= n) break; d -= n; - y += 100; + ey += 100; } - for (int n = days_per_4years(y, m); d > n; n = days_per_4years(y, m)) { + for (;;) { + int n = days_per_4years(ey, m); + if (d <= n) break; d -= n; - y += 4; + ey += 4; } - for (int n = days_per_year(y, m); d > n; n = days_per_year(y, m)) { + for (;;) { + int n = days_per_year(ey, m); + if (d <= n) break; d -= n; - ++y; + ++ey; } } if (d > 28) { - for (int n = days_per_month(y, m); d > n; n = days_per_month(y, m)) { + for (;;) { + int n = days_per_month(ey, m); + if (d <= n) break; d -= n; if (++m > 12) { - ++y; + ++ey; m = 1; } } } - return fields(y, m, static_cast<day_t>(d), hh, mm, ss); + return fields(y + (ey - oey), m, static_cast<day_t>(d), hh, mm, ss); } CONSTEXPR_F fields n_mon(year_t y, diff_t m, diff_t d, diff_t cd, hour_t hh, minute_t mm, second_t ss) noexcept { diff --git a/absl/time/internal/cctz/src/civil_time_test.cc b/absl/time/internal/cctz/src/civil_time_test.cc index be894d70..a5a71230 100644 --- a/absl/time/internal/cctz/src/civil_time_test.cc +++ b/absl/time/internal/cctz/src/civil_time_test.cc @@ -235,6 +235,16 @@ TEST(CivilTime, Difference) { } // NOTE: Run this with --copt=-ftrapv to detect overflow problems. +TEST(CivilTime, ConstructionWithHugeYear) { + constexpr civil_hour h(-9223372036854775807, 1, 1, -1); + static_assert(h.year() == -9223372036854775807 - 1, + "ConstructionWithHugeYear"); + static_assert(h.month() == 12, "ConstructionWithHugeYear"); + static_assert(h.day() == 31, "ConstructionWithHugeYear"); + static_assert(h.hour() == 23, "ConstructionWithHugeYear"); +} + +// NOTE: Run this with --copt=-ftrapv to detect overflow problems. TEST(CivilTime, DifferenceWithHugeYear) { { constexpr civil_day d1(9223372036854775807, 1, 1); diff --git a/absl/types/span.h b/absl/types/span.h index 734db695..4e450fc9 100644 --- a/absl/types/span.h +++ b/absl/types/span.h @@ -27,22 +27,17 @@ // `Span<const T>` when such types may be difficult to identify due to issues // with implicit conversion. // -// The C++ standards committee currently has a proposal for a `std::span` type, -// (http://wg21.link/p0122), which is not yet part of the standard (though may -// become part of C++20). As of August 2017, the differences between -// `absl::Span` and this proposal are: -// * `absl::Span` uses `size_t` for `size_type` -// * `absl::Span` has no `operator()` -// * `absl::Span` has no constructors for `std::unique_ptr` or -// `std::shared_ptr` +// The C++20 draft standard includes a `std::span` type. As of June 2020, the +// differences between `absl::Span` and `std::span` are: +// * `absl::Span` has `operator==` (which is likely a design bug, +// per https://abseil.io/blog/20180531-regular-types) // * `absl::Span` has the factory functions `MakeSpan()` and // `MakeConstSpan()` -// * `absl::Span` has `front()` and `back()` methods // * bounds-checked access to `absl::Span` is accomplished with `at()` // * `absl::Span` has compiler-provided move and copy constructors and // assignment. This is due to them being specified as `constexpr`, but that // implies const in C++11. -// * `absl::Span` has no `element_type` or `index_type` typedefs +// * `absl::Span` has no `element_type` typedef // * A read-only `absl::Span<const T>` can be implicitly constructed from an // initializer list. // * `absl::Span` has no `bytes()`, `size_bytes()`, `as_bytes()`, or |