550 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9// UNSUPPORTED: c++03, c++11, c++14, c++1710 11// std::ranges::rend12// std::ranges::crend13 14#include <ranges>15 16#include <cassert>17#include <utility>18#include "test_macros.h"19#include "test_iterators.h"20 21using RangeREndT = decltype(std::ranges::rend);22using RangeCREndT = decltype(std::ranges::crend);23 24static int globalBuff[8];25 26static_assert(!std::is_invocable_v<RangeREndT, int (&&)[]>);27static_assert(!std::is_invocable_v<RangeREndT, int (&)[]>);28static_assert(!std::is_invocable_v<RangeREndT, int (&&)[10]>);29static_assert( std::is_invocable_v<RangeREndT, int (&)[10]>);30static_assert(!std::is_invocable_v<RangeCREndT, int (&&)[]>);31static_assert(!std::is_invocable_v<RangeCREndT, int (&)[]>);32static_assert(!std::is_invocable_v<RangeCREndT, int (&&)[10]>);33static_assert( std::is_invocable_v<RangeCREndT, int (&)[10]>);34 35struct Incomplete;36static_assert(!std::is_invocable_v<RangeREndT, Incomplete(&&)[]>);37static_assert(!std::is_invocable_v<RangeREndT, Incomplete(&&)[42]>);38static_assert(!std::is_invocable_v<RangeCREndT, Incomplete(&&)[]>);39static_assert(!std::is_invocable_v<RangeCREndT, Incomplete(&&)[42]>);40 41struct REndMember {42 int x;43 const int* rbegin() const;44 constexpr const int* rend() const { return &x; }45};46 47// Ensure that we can't call with rvalues with borrowing disabled.48static_assert( std::is_invocable_v<RangeREndT, REndMember&>);49static_assert(!std::is_invocable_v<RangeREndT, REndMember &&>);50static_assert( std::is_invocable_v<RangeREndT, REndMember const&>);51static_assert(!std::is_invocable_v<RangeREndT, REndMember const&&>);52static_assert( std::is_invocable_v<RangeCREndT, REndMember &>);53static_assert(!std::is_invocable_v<RangeCREndT, REndMember &&>);54static_assert( std::is_invocable_v<RangeCREndT, REndMember const&>);55static_assert(!std::is_invocable_v<RangeCREndT, REndMember const&&>);56 57constexpr bool testReturnTypes() {58 {59 int *x[2];60 ASSERT_SAME_TYPE(decltype(std::ranges::rend(x)), std::reverse_iterator<int**>);61 ASSERT_SAME_TYPE(decltype(std::ranges::crend(x)), std::reverse_iterator<int* const*>);62 }63 64 {65 int x[2][2];66 ASSERT_SAME_TYPE(decltype(std::ranges::rend(x)), std::reverse_iterator<int(*)[2]>);67 ASSERT_SAME_TYPE(decltype(std::ranges::crend(x)), std::reverse_iterator<const int(*)[2]>);68 }69 70 {71 struct Different {72 char* rbegin();73 sentinel_wrapper<char*>& rend();74 short* rbegin() const;75 sentinel_wrapper<short*>& rend() const;76 } x;77 ASSERT_SAME_TYPE(decltype(std::ranges::rend(x)), sentinel_wrapper<char*>);78 ASSERT_SAME_TYPE(decltype(std::ranges::crend(x)), sentinel_wrapper<short*>);79 }80 81 return true;82}83 84constexpr bool testArray() {85 int a[2];86 assert(std::ranges::rend(a).base() == a);87 assert(std::ranges::crend(a).base() == a);88 89 int b[2][2];90 assert(std::ranges::rend(b).base() == b);91 assert(std::ranges::crend(b).base() == b);92 93 REndMember c[2];94 assert(std::ranges::rend(c).base() == c);95 assert(std::ranges::crend(c).base() == c);96 97 return true;98}99 100struct REndMemberReturnsInt {101 int rbegin() const;102 int rend() const;103};104static_assert(!std::is_invocable_v<RangeREndT, REndMemberReturnsInt const&>);105 106struct REndMemberReturnsVoidPtr {107 const void *rbegin() const;108 const void *rend() const;109};110static_assert(!std::is_invocable_v<RangeREndT, REndMemberReturnsVoidPtr const&>);111 112struct PtrConvertible {113 operator int*() const;114};115struct PtrConvertibleREndMember {116 PtrConvertible rbegin() const;117 PtrConvertible rend() const;118};119static_assert(!std::is_invocable_v<RangeREndT, PtrConvertibleREndMember const&>);120 121struct NoRBeginMember {122 constexpr const int* rend();123};124static_assert(!std::is_invocable_v<RangeREndT, NoRBeginMember const&>);125 126struct NonConstREndMember {127 int x;128 constexpr int* rbegin() { return nullptr; }129 constexpr int* rend() { return &x; }130};131static_assert( std::is_invocable_v<RangeREndT, NonConstREndMember &>);132static_assert(!std::is_invocable_v<RangeREndT, NonConstREndMember const&>);133static_assert(!std::is_invocable_v<RangeCREndT, NonConstREndMember &>);134static_assert(!std::is_invocable_v<RangeCREndT, NonConstREndMember const&>);135 136struct EnabledBorrowingREndMember {137 constexpr int* rbegin() const { return nullptr; }138 constexpr int* rend() const { return &globalBuff[0]; }139};140 141template <>142inline constexpr bool std::ranges::enable_borrowed_range<EnabledBorrowingREndMember> = true;143 144struct REndMemberFunction {145 int x;146 constexpr const int* rbegin() const { return nullptr; }147 constexpr const int* rend() const { return &x; }148 friend constexpr int* rend(REndMemberFunction const&);149};150 151struct Empty { };152struct EmptyEndMember {153 Empty rbegin() const;154 Empty rend() const;155};156static_assert(!std::is_invocable_v<RangeREndT, EmptyEndMember const&>);157 158struct EmptyPtrREndMember {159 Empty x;160 constexpr const Empty* rbegin() const { return nullptr; }161 constexpr const Empty* rend() const { return &x; }162};163 164constexpr bool testREndMember() {165 REndMember a;166 assert(std::ranges::rend(a) == &a.x);167 assert(std::ranges::crend(a) == &a.x);168 169 NonConstREndMember b;170 assert(std::ranges::rend(b) == &b.x);171 static_assert(!std::is_invocable_v<RangeCREndT, decltype((b))>);172 173 EnabledBorrowingREndMember c;174 assert(std::ranges::rend(std::move(c)) == &globalBuff[0]);175 assert(std::ranges::crend(std::move(c)) == &globalBuff[0]);176 177 REndMemberFunction d;178 assert(std::ranges::rend(d) == &d.x);179 assert(std::ranges::crend(d) == &d.x);180 181 EmptyPtrREndMember e;182 assert(std::ranges::rend(e) == &e.x);183 assert(std::ranges::crend(e) == &e.x);184 185 return true;186}187 188struct REndFunction {189 int x;190 friend constexpr const int* rbegin(REndFunction const&) { return nullptr; }191 friend constexpr const int* rend(REndFunction const& bf) { return &bf.x; }192};193 194static_assert( std::is_invocable_v<RangeREndT, REndFunction const&>);195static_assert(!std::is_invocable_v<RangeREndT, REndFunction &&>);196 197static_assert( std::is_invocable_v<RangeREndT, REndFunction const&>);198static_assert(!std::is_invocable_v<RangeREndT, REndFunction &&>);199static_assert(std::is_invocable_v<RangeREndT, REndFunction&>); // Ill-formed before P2602R2 Poison Pills are Too Toxic200static_assert( std::is_invocable_v<RangeCREndT, REndFunction const&>);201static_assert( std::is_invocable_v<RangeCREndT, REndFunction &>);202 203struct REndFunctionReturnsInt {204 friend constexpr int rbegin(REndFunctionReturnsInt const&);205 friend constexpr int rend(REndFunctionReturnsInt const&);206};207static_assert(!std::is_invocable_v<RangeREndT, REndFunctionReturnsInt const&>);208 209struct REndFunctionReturnsVoidPtr {210 friend constexpr void* rbegin(REndFunctionReturnsVoidPtr const&);211 friend constexpr void* rend(REndFunctionReturnsVoidPtr const&);212};213static_assert(!std::is_invocable_v<RangeREndT, REndFunctionReturnsVoidPtr const&>);214 215struct REndFunctionReturnsEmpty {216 friend constexpr Empty rbegin(REndFunctionReturnsEmpty const&);217 friend constexpr Empty rend(REndFunctionReturnsEmpty const&);218};219static_assert(!std::is_invocable_v<RangeREndT, REndFunctionReturnsEmpty const&>);220 221struct REndFunctionReturnsPtrConvertible {222 friend constexpr PtrConvertible rbegin(REndFunctionReturnsPtrConvertible const&);223 friend constexpr PtrConvertible rend(REndFunctionReturnsPtrConvertible const&);224};225static_assert(!std::is_invocable_v<RangeREndT, REndFunctionReturnsPtrConvertible const&>);226 227struct NoRBeginFunction {228 friend constexpr const int* rend(NoRBeginFunction const&);229};230static_assert(!std::is_invocable_v<RangeREndT, NoRBeginFunction const&>);231 232struct REndFunctionByValue {233 friend constexpr int* rbegin(REndFunctionByValue) { return nullptr; }234 friend constexpr int* rend(REndFunctionByValue) { return &globalBuff[1]; }235};236static_assert(!std::is_invocable_v<RangeCREndT, REndFunctionByValue>);237 238struct REndFunctionEnabledBorrowing {239 friend constexpr int* rbegin(REndFunctionEnabledBorrowing) { return nullptr; }240 friend constexpr int* rend(REndFunctionEnabledBorrowing) { return &globalBuff[2]; }241};242template<>243inline constexpr bool std::ranges::enable_borrowed_range<REndFunctionEnabledBorrowing> = true;244 245struct REndFunctionReturnsEmptyPtr {246 Empty x;247 friend constexpr const Empty* rbegin(REndFunctionReturnsEmptyPtr const&) { return nullptr; }248 friend constexpr const Empty* rend(REndFunctionReturnsEmptyPtr const& bf) { return &bf.x; }249};250 251struct REndFunctionWithDataMember {252 int x;253 int rend;254 friend constexpr const int* rbegin(REndFunctionWithDataMember const&) { return nullptr; }255 friend constexpr const int* rend(REndFunctionWithDataMember const& bf) { return &bf.x; }256};257 258struct REndFunctionWithPrivateEndMember : private REndMember {259 int y;260 friend constexpr const int* rbegin(REndFunctionWithPrivateEndMember const&) { return nullptr; }261 friend constexpr const int* rend(REndFunctionWithPrivateEndMember const& bf) { return &bf.y; }262};263 264struct RBeginMemberEndFunction {265 int x;266 constexpr const int* rbegin() const { return nullptr; }267 friend constexpr const int* rend(RBeginMemberEndFunction const& bf) { return &bf.x; }268};269 270constexpr bool testREndFunction() {271 const REndFunction a{};272 assert(std::ranges::rend(a) == &a.x);273 assert(std::ranges::crend(a) == &a.x);274 REndFunction aa{};275 assert(std::ranges::rend(aa) == &aa.x); // Ill-formed before P2602R2 Poison Pills are Too Toxic276 assert(std::ranges::crend(aa) == &aa.x);277 278 REndFunctionByValue b;279 assert(std::ranges::rend(b) == &globalBuff[1]);280 assert(std::ranges::crend(b) == &globalBuff[1]);281 282 REndFunctionEnabledBorrowing c;283 assert(std::ranges::rend(std::move(c)) == &globalBuff[2]);284 assert(std::ranges::crend(std::move(c)) == &globalBuff[2]);285 286 const REndFunctionReturnsEmptyPtr d{};287 assert(std::ranges::rend(d) == &d.x);288 assert(std::ranges::crend(d) == &d.x);289 REndFunctionReturnsEmptyPtr dd{};290 assert(std::ranges::rend(dd) == &dd.x); // Ill-formed before P2602R2 Poison Pills are Too Toxic291 assert(std::ranges::crend(dd) == &dd.x);292 293 const REndFunctionWithDataMember e{};294 assert(std::ranges::rend(e) == &e.x);295 assert(std::ranges::crend(e) == &e.x);296 REndFunctionWithDataMember ee{};297 assert(std::ranges::rend(ee) == &ee.x); // Ill-formed before P2602R2 Poison Pills are Too Toxic298 assert(std::ranges::crend(ee) == &ee.x);299 300 const REndFunctionWithPrivateEndMember f{};301 assert(std::ranges::rend(f) == &f.y);302 assert(std::ranges::crend(f) == &f.y);303 REndFunctionWithPrivateEndMember ff{};304 assert(std::ranges::rend(ff) == &ff.y); // Ill-formed before P2602R2 Poison Pills are Too Toxic305 assert(std::ranges::crend(ff) == &ff.y);306 307 const RBeginMemberEndFunction g{};308 assert(std::ranges::rend(g) == &g.x);309 assert(std::ranges::crend(g) == &g.x);310 RBeginMemberEndFunction gg{};311 assert(std::ranges::rend(gg) == &gg.x); // Ill-formed before P2602R2 Poison Pills are Too Toxic312 assert(std::ranges::crend(gg) == &gg.x);313 314 return true;315}316 317 318struct MemberBeginEnd {319 int b, e;320 char cb, ce;321 constexpr bidirectional_iterator<int*> begin() { return bidirectional_iterator<int*>(&b); }322 constexpr bidirectional_iterator<int*> end() { return bidirectional_iterator<int*>(&e); }323 constexpr bidirectional_iterator<const char*> begin() const { return bidirectional_iterator<const char*>(&cb); }324 constexpr bidirectional_iterator<const char*> end() const { return bidirectional_iterator<const char*>(&ce); }325};326static_assert( std::is_invocable_v<RangeREndT, MemberBeginEnd&>);327static_assert( std::is_invocable_v<RangeREndT, MemberBeginEnd const&>);328static_assert( std::is_invocable_v<RangeCREndT, MemberBeginEnd const&>);329 330struct FunctionBeginEnd {331 int b, e;332 char cb, ce;333 friend constexpr bidirectional_iterator<int*> begin(FunctionBeginEnd& v) {334 return bidirectional_iterator<int*>(&v.b);335 }336 friend constexpr bidirectional_iterator<int*> end(FunctionBeginEnd& v) { return bidirectional_iterator<int*>(&v.e); }337 friend constexpr bidirectional_iterator<const char*> begin(const FunctionBeginEnd& v) {338 return bidirectional_iterator<const char*>(&v.cb);339 }340 friend constexpr bidirectional_iterator<const char*> end(const FunctionBeginEnd& v) {341 return bidirectional_iterator<const char*>(&v.ce);342 }343};344static_assert( std::is_invocable_v<RangeREndT, FunctionBeginEnd&>);345static_assert( std::is_invocable_v<RangeREndT, FunctionBeginEnd const&>);346static_assert( std::is_invocable_v<RangeCREndT, FunctionBeginEnd const&>);347 348struct MemberBeginFunctionEnd {349 int b, e;350 char cb, ce;351 constexpr bidirectional_iterator<int*> begin() { return bidirectional_iterator<int*>(&b); }352 friend constexpr bidirectional_iterator<int*> end(MemberBeginFunctionEnd& v) {353 return bidirectional_iterator<int*>(&v.e);354 }355 constexpr bidirectional_iterator<const char*> begin() const { return bidirectional_iterator<const char*>(&cb); }356 friend constexpr bidirectional_iterator<const char*> end(const MemberBeginFunctionEnd& v) {357 return bidirectional_iterator<const char*>(&v.ce);358 }359};360static_assert( std::is_invocable_v<RangeREndT, MemberBeginFunctionEnd&>);361static_assert( std::is_invocable_v<RangeREndT, MemberBeginFunctionEnd const&>);362static_assert( std::is_invocable_v<RangeCREndT, MemberBeginFunctionEnd const&>);363 364struct FunctionBeginMemberEnd {365 int b, e;366 char cb, ce;367 friend constexpr bidirectional_iterator<int*> begin(FunctionBeginMemberEnd& v) {368 return bidirectional_iterator<int*>(&v.b);369 }370 constexpr bidirectional_iterator<int*> end() { return bidirectional_iterator<int*>(&e); }371 friend constexpr bidirectional_iterator<const char*> begin(const FunctionBeginMemberEnd& v) {372 return bidirectional_iterator<const char*>(&v.cb);373 }374 constexpr bidirectional_iterator<const char*> end() const { return bidirectional_iterator<const char*>(&ce); }375};376static_assert( std::is_invocable_v<RangeREndT, FunctionBeginMemberEnd&>);377static_assert( std::is_invocable_v<RangeREndT, FunctionBeginMemberEnd const&>);378static_assert( std::is_invocable_v<RangeCREndT, FunctionBeginMemberEnd const&>);379 380struct MemberBeginEndDifferentTypes {381 bidirectional_iterator<int*> begin();382 bidirectional_iterator<const int*> end();383};384static_assert(!std::is_invocable_v<RangeREndT, MemberBeginEndDifferentTypes&>);385static_assert(!std::is_invocable_v<RangeCREndT, MemberBeginEndDifferentTypes&>);386 387struct FunctionBeginEndDifferentTypes {388 friend bidirectional_iterator<int*> begin(FunctionBeginEndDifferentTypes&);389 friend bidirectional_iterator<const int*> end(FunctionBeginEndDifferentTypes&);390};391static_assert(!std::is_invocable_v<RangeREndT, FunctionBeginEndDifferentTypes&>);392static_assert(!std::is_invocable_v<RangeCREndT, FunctionBeginEndDifferentTypes&>);393 394struct MemberBeginEndForwardIterators {395 forward_iterator<int*> begin();396 forward_iterator<int*> end();397};398static_assert(!std::is_invocable_v<RangeREndT, MemberBeginEndForwardIterators&>);399static_assert(!std::is_invocable_v<RangeCREndT, MemberBeginEndForwardIterators&>);400 401struct FunctionBeginEndForwardIterators {402 friend forward_iterator<int*> begin(FunctionBeginEndForwardIterators&);403 friend forward_iterator<int*> end(FunctionBeginEndForwardIterators&);404};405static_assert(!std::is_invocable_v<RangeREndT, FunctionBeginEndForwardIterators&>);406static_assert(!std::is_invocable_v<RangeCREndT, FunctionBeginEndForwardIterators&>);407 408struct MemberBeginOnly {409 bidirectional_iterator<int*> begin() const;410};411static_assert(!std::is_invocable_v<RangeREndT, MemberBeginOnly&>);412static_assert(!std::is_invocable_v<RangeCREndT, MemberBeginOnly&>);413 414struct FunctionBeginOnly {415 friend bidirectional_iterator<int*> begin(FunctionBeginOnly&);416};417static_assert(!std::is_invocable_v<RangeREndT, FunctionBeginOnly&>);418static_assert(!std::is_invocable_v<RangeCREndT, FunctionBeginOnly&>);419 420struct MemberEndOnly {421 bidirectional_iterator<int*> end() const;422};423static_assert(!std::is_invocable_v<RangeREndT, MemberEndOnly&>);424static_assert(!std::is_invocable_v<RangeCREndT, MemberEndOnly&>);425 426struct FunctionEndOnly {427 friend bidirectional_iterator<int*> end(FunctionEndOnly&);428};429static_assert(!std::is_invocable_v<RangeREndT, FunctionEndOnly&>);430static_assert(!std::is_invocable_v<RangeCREndT, FunctionEndOnly&>);431 432// Make sure there is no clash between the following cases:433// - the case that handles classes defining member `rbegin` and `rend` functions;434// - the case that handles classes defining `begin` and `end` functions returning reversible iterators.435struct MemberBeginAndRBegin {436 int* begin() const;437 int* end() const;438 int* rbegin() const;439 int* rend() const;440};441static_assert( std::is_invocable_v<RangeREndT, MemberBeginAndRBegin&>);442static_assert( std::is_invocable_v<RangeCREndT, MemberBeginAndRBegin&>);443static_assert( std::same_as<std::invoke_result_t<RangeREndT, MemberBeginAndRBegin&>, int*>);444static_assert( std::same_as<std::invoke_result_t<RangeCREndT, MemberBeginAndRBegin&>, int*>);445 446constexpr bool testBeginEnd() {447 MemberBeginEnd a{};448 const MemberBeginEnd aa{};449 assert(base(std::ranges::rend(a).base()) == &a.b);450 assert(base(std::ranges::crend(a).base()) == &a.cb);451 assert(base(std::ranges::rend(aa).base()) == &aa.cb);452 assert(base(std::ranges::crend(aa).base()) == &aa.cb);453 454 FunctionBeginEnd b{};455 const FunctionBeginEnd bb{};456 assert(base(std::ranges::rend(b).base()) == &b.b);457 assert(base(std::ranges::crend(b).base()) == &b.cb);458 assert(base(std::ranges::rend(bb).base()) == &bb.cb);459 assert(base(std::ranges::crend(bb).base()) == &bb.cb);460 461 MemberBeginFunctionEnd c{};462 const MemberBeginFunctionEnd cc{};463 assert(base(std::ranges::rend(c).base()) == &c.b);464 assert(base(std::ranges::crend(c).base()) == &c.cb);465 assert(base(std::ranges::rend(cc).base()) == &cc.cb);466 assert(base(std::ranges::crend(cc).base()) == &cc.cb);467 468 FunctionBeginMemberEnd d{};469 const FunctionBeginMemberEnd dd{};470 assert(base(std::ranges::rend(d).base()) == &d.b);471 assert(base(std::ranges::crend(d).base()) == &d.cb);472 assert(base(std::ranges::rend(dd).base()) == &dd.cb);473 assert(base(std::ranges::crend(dd).base()) == &dd.cb);474 475 return true;476}477 478 479ASSERT_NOEXCEPT(std::ranges::rend(std::declval<int (&)[10]>()));480ASSERT_NOEXCEPT(std::ranges::crend(std::declval<int (&)[10]>()));481 482struct NoThrowMemberREnd {483 ThrowingIterator<int> rbegin() const;484 ThrowingIterator<int> rend() const noexcept; // auto(t.rend()) doesn't throw485} ntmre;486static_assert(noexcept(std::ranges::rend(ntmre)));487static_assert(noexcept(std::ranges::crend(ntmre)));488 489struct NoThrowADLREnd {490 ThrowingIterator<int> rbegin() const;491 friend ThrowingIterator<int> rend(NoThrowADLREnd&) noexcept; // auto(rend(t)) doesn't throw492 friend ThrowingIterator<int> rend(const NoThrowADLREnd&) noexcept;493} ntare;494static_assert(noexcept(std::ranges::rend(ntare)));495static_assert(noexcept(std::ranges::crend(ntare)));496 497struct NoThrowMemberREndReturnsRef {498 ThrowingIterator<int> rbegin() const;499 ThrowingIterator<int>& rend() const noexcept; // auto(t.rend()) may throw500} ntmrerr;501static_assert(!noexcept(std::ranges::rend(ntmrerr)));502static_assert(!noexcept(std::ranges::crend(ntmrerr)));503 504struct REndReturnsArrayRef {505 auto rbegin() const noexcept -> int(&)[10];506 auto rend() const noexcept -> int(&)[10];507} rerar;508static_assert(noexcept(std::ranges::rend(rerar)));509static_assert(noexcept(std::ranges::crend(rerar)));510 511struct NoThrowBeginThrowingEnd {512 int* begin() const noexcept;513 int* end() const;514} ntbte;515static_assert(noexcept(std::ranges::rend(ntbte)));516static_assert(noexcept(std::ranges::crend(ntbte)));517 518struct NoThrowEndThrowingBegin {519 int* begin() const;520 int* end() const noexcept;521} ntetb;522static_assert(!noexcept(std::ranges::rend(ntetb)));523static_assert(!noexcept(std::ranges::crend(ntetb)));524 525// Test ADL-proofing.526struct Incomplete;527template<class T> struct Holder { T t; };528static_assert(!std::is_invocable_v<RangeREndT, Holder<Incomplete>*>);529static_assert(!std::is_invocable_v<RangeREndT, Holder<Incomplete>*&>);530static_assert(!std::is_invocable_v<RangeCREndT, Holder<Incomplete>*>);531static_assert(!std::is_invocable_v<RangeCREndT, Holder<Incomplete>*&>);532 533int main(int, char**) {534 static_assert(testReturnTypes());535 536 testArray();537 static_assert(testArray());538 539 testREndMember();540 static_assert(testREndMember());541 542 testREndFunction();543 static_assert(testREndFunction());544 545 testBeginEnd();546 static_assert(testBeginEnd());547 548 return 0;549}550