mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-12-13 19:24:21 +00:00
Constant expression evaluation: preserve subobject designator when flattening a
core constant value down to an APValue. llvm-svn: 143909
This commit is contained in:
parent
c109a259d2
commit
8081560048
@ -22,6 +22,7 @@ namespace clang {
|
|||||||
class CharUnits;
|
class CharUnits;
|
||||||
class DiagnosticBuilder;
|
class DiagnosticBuilder;
|
||||||
class Expr;
|
class Expr;
|
||||||
|
class Decl;
|
||||||
|
|
||||||
/// APValue - This class implements a discriminated union of [uninitialized]
|
/// APValue - This class implements a discriminated union of [uninitialized]
|
||||||
/// [APSInt] [APFloat], [Complex APSInt] [Complex APFloat], [Expr + Offset].
|
/// [APSInt] [APFloat], [Complex APSInt] [Complex APFloat], [Expr + Offset].
|
||||||
@ -38,6 +39,11 @@ public:
|
|||||||
LValue,
|
LValue,
|
||||||
Vector
|
Vector
|
||||||
};
|
};
|
||||||
|
union LValuePathEntry {
|
||||||
|
const Decl *BaseOrMember;
|
||||||
|
uint64_t ArrayIndex;
|
||||||
|
};
|
||||||
|
struct NoLValuePath {};
|
||||||
private:
|
private:
|
||||||
ValueKind Kind;
|
ValueKind Kind;
|
||||||
|
|
||||||
@ -57,6 +63,8 @@ private:
|
|||||||
~Vec() { delete[] Elts; }
|
~Vec() { delete[] Elts; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LV;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
MaxSize = (sizeof(ComplexAPSInt) > sizeof(ComplexAPFloat) ?
|
MaxSize = (sizeof(ComplexAPSInt) > sizeof(ComplexAPFloat) ?
|
||||||
sizeof(ComplexAPSInt) : sizeof(ComplexAPFloat))
|
sizeof(ComplexAPSInt) : sizeof(ComplexAPFloat))
|
||||||
@ -87,10 +95,15 @@ public:
|
|||||||
APValue(const APValue &RHS) : Kind(Uninitialized) {
|
APValue(const APValue &RHS) : Kind(Uninitialized) {
|
||||||
*this = RHS;
|
*this = RHS;
|
||||||
}
|
}
|
||||||
APValue(const Expr* B, const CharUnits &O) : Kind(Uninitialized) {
|
APValue(const Expr *B, const CharUnits &O, NoLValuePath N)
|
||||||
MakeLValue(); setLValue(B, O);
|
: Kind(Uninitialized) {
|
||||||
|
MakeLValue(); setLValue(B, O, N);
|
||||||
}
|
}
|
||||||
APValue(const Expr* B);
|
APValue(const Expr *B, const CharUnits &O, ArrayRef<LValuePathEntry> Path)
|
||||||
|
: Kind(Uninitialized) {
|
||||||
|
MakeLValue(); setLValue(B, O, Path);
|
||||||
|
}
|
||||||
|
APValue(const Expr *B);
|
||||||
|
|
||||||
~APValue() {
|
~APValue() {
|
||||||
MakeUninit();
|
MakeUninit();
|
||||||
@ -174,6 +187,8 @@ public:
|
|||||||
const CharUnits &getLValueOffset() const {
|
const CharUnits &getLValueOffset() const {
|
||||||
return const_cast<APValue*>(this)->getLValueOffset();
|
return const_cast<APValue*>(this)->getLValueOffset();
|
||||||
}
|
}
|
||||||
|
bool hasLValuePath() const;
|
||||||
|
ArrayRef<LValuePathEntry> getLValuePath() const;
|
||||||
|
|
||||||
void setInt(const APSInt &I) {
|
void setInt(const APSInt &I) {
|
||||||
assert(isInt() && "Invalid accessor");
|
assert(isInt() && "Invalid accessor");
|
||||||
@ -204,7 +219,9 @@ public:
|
|||||||
((ComplexAPFloat*)(char*)Data)->Real = R;
|
((ComplexAPFloat*)(char*)Data)->Real = R;
|
||||||
((ComplexAPFloat*)(char*)Data)->Imag = I;
|
((ComplexAPFloat*)(char*)Data)->Imag = I;
|
||||||
}
|
}
|
||||||
void setLValue(const Expr *B, const CharUnits &O);
|
void setLValue(const Expr *B, const CharUnits &O, NoLValuePath);
|
||||||
|
void setLValue(const Expr *B, const CharUnits &O,
|
||||||
|
ArrayRef<LValuePathEntry> Path);
|
||||||
|
|
||||||
const APValue &operator=(const APValue &RHS);
|
const APValue &operator=(const APValue &RHS);
|
||||||
|
|
||||||
|
@ -500,10 +500,6 @@ public:
|
|||||||
/// lvalue with link time known address, with no side-effects.
|
/// lvalue with link time known address, with no side-effects.
|
||||||
bool EvaluateAsLValue(EvalResult &Result, const ASTContext &Ctx) const;
|
bool EvaluateAsLValue(EvalResult &Result, const ASTContext &Ctx) const;
|
||||||
|
|
||||||
/// EvaluateAsLValue - Evaluate an expression to see if we can fold it to an
|
|
||||||
/// lvalue, even if the expression has side-effects.
|
|
||||||
bool EvaluateAsAnyLValue(EvalResult &Result, const ASTContext &Ctx) const;
|
|
||||||
|
|
||||||
/// \brief Enumeration used to describe the kind of Null pointer constant
|
/// \brief Enumeration used to describe the kind of Null pointer constant
|
||||||
/// returned from \c isNullPointerConstant().
|
/// returned from \c isNullPointerConstant().
|
||||||
enum NullPointerConstantKind {
|
enum NullPointerConstantKind {
|
||||||
|
@ -20,14 +20,41 @@
|
|||||||
using namespace clang;
|
using namespace clang;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct LV {
|
struct LVBase {
|
||||||
const Expr* Base;
|
const Expr *Base;
|
||||||
CharUnits Offset;
|
CharUnits Offset;
|
||||||
|
unsigned PathLength;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct APValue::LV : LVBase {
|
||||||
|
static const unsigned InlinePathSpace =
|
||||||
|
(MaxSize - sizeof(LVBase)) / sizeof(LValuePathEntry);
|
||||||
|
|
||||||
|
/// Path - The sequence of base classes, fields and array indices to follow to
|
||||||
|
/// walk from Base to the subobject. When performing GCC-style folding, there
|
||||||
|
/// may not be such a path.
|
||||||
|
union {
|
||||||
|
LValuePathEntry Path[InlinePathSpace];
|
||||||
|
LValuePathEntry *PathPtr;
|
||||||
|
};
|
||||||
|
|
||||||
|
LV() { PathLength = (unsigned)-1; }
|
||||||
|
~LV() { if (hasPathPtr()) delete [] PathPtr; }
|
||||||
|
|
||||||
|
void allocPath() {
|
||||||
|
if (hasPathPtr()) PathPtr = new LValuePathEntry[PathLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasPath() const { return PathLength != (unsigned)-1; }
|
||||||
|
bool hasPathPtr() const { return hasPath() && PathLength > InlinePathSpace; }
|
||||||
|
|
||||||
|
LValuePathEntry *getPath() { return hasPathPtr() ? PathPtr : Path; }
|
||||||
|
};
|
||||||
|
|
||||||
APValue::APValue(const Expr* B) : Kind(Uninitialized) {
|
APValue::APValue(const Expr* B) : Kind(Uninitialized) {
|
||||||
MakeLValue(); setLValue(B, CharUnits::Zero());
|
MakeLValue();
|
||||||
|
setLValue(B, CharUnits::Zero(), ArrayRef<LValuePathEntry>());
|
||||||
}
|
}
|
||||||
|
|
||||||
const APValue &APValue::operator=(const APValue &RHS) {
|
const APValue &APValue::operator=(const APValue &RHS) {
|
||||||
@ -57,8 +84,12 @@ const APValue &APValue::operator=(const APValue &RHS) {
|
|||||||
setComplexInt(RHS.getComplexIntReal(), RHS.getComplexIntImag());
|
setComplexInt(RHS.getComplexIntReal(), RHS.getComplexIntImag());
|
||||||
else if (isComplexFloat())
|
else if (isComplexFloat())
|
||||||
setComplexFloat(RHS.getComplexFloatReal(), RHS.getComplexFloatImag());
|
setComplexFloat(RHS.getComplexFloatReal(), RHS.getComplexFloatImag());
|
||||||
else if (isLValue())
|
else if (isLValue()) {
|
||||||
setLValue(RHS.getLValueBase(), RHS.getLValueOffset());
|
if (RHS.hasLValuePath())
|
||||||
|
setLValue(RHS.getLValueBase(), RHS.getLValueOffset(),RHS.getLValuePath());
|
||||||
|
else
|
||||||
|
setLValue(RHS.getLValueBase(), RHS.getLValueOffset(), NoLValuePath());
|
||||||
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,14 +205,38 @@ CharUnits &APValue::getLValueOffset() {
|
|||||||
return ((LV*)(void*)Data)->Offset;
|
return ((LV*)(void*)Data)->Offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APValue::setLValue(const Expr *B, const CharUnits &O) {
|
bool APValue::hasLValuePath() const {
|
||||||
assert(isLValue() && "Invalid accessor");
|
assert(isLValue() && "Invalid accessor");
|
||||||
((LV*)(char*)Data)->Base = B;
|
return ((LV*)(char*)Data)->hasPath();
|
||||||
((LV*)(char*)Data)->Offset = O;
|
}
|
||||||
|
|
||||||
|
ArrayRef<APValue::LValuePathEntry> APValue::getLValuePath() const {
|
||||||
|
assert(isLValue() && hasLValuePath() && "Invalid accessor");
|
||||||
|
LV &LVal = *((LV*)(char*)Data);
|
||||||
|
return ArrayRef<LValuePathEntry>(LVal.getPath(), LVal.PathLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void APValue::setLValue(const Expr *B, const CharUnits &O, NoLValuePath) {
|
||||||
|
assert(isLValue() && "Invalid accessor");
|
||||||
|
LV &LVal = *((LV*)(char*)Data);
|
||||||
|
LVal.Base = B;
|
||||||
|
LVal.Offset = O;
|
||||||
|
LVal.PathLength = (unsigned)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void APValue::setLValue(const Expr *B, const CharUnits &O,
|
||||||
|
ArrayRef<LValuePathEntry> Path) {
|
||||||
|
assert(isLValue() && "Invalid accessor");
|
||||||
|
LV &LVal = *((LV*)(char*)Data);
|
||||||
|
LVal.Base = B;
|
||||||
|
LVal.Offset = O;
|
||||||
|
LVal.PathLength = Path.size();
|
||||||
|
memcpy(LVal.getPath(), Path.data(), Path.size() * sizeof(LValuePathEntry));
|
||||||
}
|
}
|
||||||
|
|
||||||
void APValue::MakeLValue() {
|
void APValue::MakeLValue() {
|
||||||
assert(isUninit() && "Bad state change");
|
assert(isUninit() && "Bad state change");
|
||||||
|
assert(sizeof(LV) <= MaxSize && "LV too big");
|
||||||
new ((void*)(char*)Data) LV();
|
new ((void*)(char*)Data) LV();
|
||||||
Kind = LValue;
|
Kind = LValue;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,24 @@ namespace {
|
|||||||
struct CallStackFrame;
|
struct CallStackFrame;
|
||||||
struct EvalInfo;
|
struct EvalInfo;
|
||||||
|
|
||||||
|
/// Determine whether the described subobject is an array element.
|
||||||
|
static bool SubobjectIsArrayElement(QualType Base,
|
||||||
|
ArrayRef<APValue::LValuePathEntry> Path) {
|
||||||
|
bool IsArrayElement = false;
|
||||||
|
const Type *T = Base.getTypePtr();
|
||||||
|
for (unsigned I = 0, N = Path.size(); I != N; ++I) {
|
||||||
|
IsArrayElement = T && T->isArrayType();
|
||||||
|
if (IsArrayElement)
|
||||||
|
T = T->getBaseElementTypeUnsafe();
|
||||||
|
else if (const FieldDecl *FD = dyn_cast<FieldDecl>(Path[I].BaseOrMember))
|
||||||
|
T = FD->getType().getTypePtr();
|
||||||
|
else
|
||||||
|
// Path[I] describes a base class.
|
||||||
|
T = 0;
|
||||||
|
}
|
||||||
|
return IsArrayElement;
|
||||||
|
}
|
||||||
|
|
||||||
/// A path from a glvalue to a subobject of that glvalue.
|
/// A path from a glvalue to a subobject of that glvalue.
|
||||||
struct SubobjectDesignator {
|
struct SubobjectDesignator {
|
||||||
/// True if the subobject was named in a manner not supported by C++11. Such
|
/// True if the subobject was named in a manner not supported by C++11. Such
|
||||||
@ -59,20 +77,28 @@ namespace {
|
|||||||
/// Whether this designates 'one past the end' of the current subobject.
|
/// Whether this designates 'one past the end' of the current subobject.
|
||||||
bool OnePastTheEnd : 1;
|
bool OnePastTheEnd : 1;
|
||||||
|
|
||||||
union PathEntry {
|
typedef APValue::LValuePathEntry PathEntry;
|
||||||
/// If the current subobject is of class type, this indicates which
|
|
||||||
/// subobject of that type is accessed next.
|
|
||||||
const Decl *BaseOrMember;
|
|
||||||
/// If the current subobject is of array type, this indicates which index
|
|
||||||
/// within that array is accessed next.
|
|
||||||
uint64_t Index;
|
|
||||||
};
|
|
||||||
/// The entries on the path from the glvalue to the designated subobject.
|
/// The entries on the path from the glvalue to the designated subobject.
|
||||||
SmallVector<PathEntry, 8> Entries;
|
SmallVector<PathEntry, 8> Entries;
|
||||||
|
|
||||||
SubobjectDesignator() :
|
SubobjectDesignator() :
|
||||||
Invalid(false), ArrayElement(false), OnePastTheEnd(false) {}
|
Invalid(false), ArrayElement(false), OnePastTheEnd(false) {}
|
||||||
|
|
||||||
|
SubobjectDesignator(const APValue &V) :
|
||||||
|
Invalid(!V.isLValue() || !V.hasLValuePath()), ArrayElement(false),
|
||||||
|
OnePastTheEnd(false) {
|
||||||
|
if (!Invalid) {
|
||||||
|
ArrayRef<PathEntry> VEntries = V.getLValuePath();
|
||||||
|
Entries.insert(Entries.end(), VEntries.begin(), VEntries.end());
|
||||||
|
if (V.getLValueBase())
|
||||||
|
ArrayElement = SubobjectIsArrayElement(V.getLValueBase()->getType(),
|
||||||
|
V.getLValuePath());
|
||||||
|
else
|
||||||
|
assert(V.getLValuePath().empty() &&"Null pointer with nonempty path");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setInvalid() {
|
void setInvalid() {
|
||||||
Invalid = true;
|
Invalid = true;
|
||||||
Entries.clear();
|
Entries.clear();
|
||||||
@ -85,7 +111,7 @@ namespace {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PathEntry Entry;
|
PathEntry Entry;
|
||||||
Entry.Index = N;
|
Entry.ArrayIndex = N;
|
||||||
Entries.push_back(Entry);
|
Entries.push_back(Entry);
|
||||||
ArrayElement = true;
|
ArrayElement = true;
|
||||||
}
|
}
|
||||||
@ -106,7 +132,7 @@ namespace {
|
|||||||
void adjustIndex(uint64_t N) {
|
void adjustIndex(uint64_t N) {
|
||||||
if (Invalid) return;
|
if (Invalid) return;
|
||||||
if (ArrayElement) {
|
if (ArrayElement) {
|
||||||
Entries.back().Index += N;
|
Entries.back().ArrayIndex += N;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (OnePastTheEnd && N == (uint64_t)-1)
|
if (OnePastTheEnd && N == (uint64_t)-1)
|
||||||
@ -141,9 +167,9 @@ namespace {
|
|||||||
CCValue(const CCValue &V) : APValue(V), CallFrame(V.CallFrame) {}
|
CCValue(const CCValue &V) : APValue(V), CallFrame(V.CallFrame) {}
|
||||||
CCValue(const Expr *B, const CharUnits &O, CallStackFrame *F,
|
CCValue(const Expr *B, const CharUnits &O, CallStackFrame *F,
|
||||||
const SubobjectDesignator &D) :
|
const SubobjectDesignator &D) :
|
||||||
APValue(B, O), CallFrame(F), Designator(D) {}
|
APValue(B, O, APValue::NoLValuePath()), CallFrame(F), Designator(D) {}
|
||||||
CCValue(const APValue &V, GlobalValue) :
|
CCValue(const APValue &V, GlobalValue) :
|
||||||
APValue(V), CallFrame(0), Designator() {}
|
APValue(V), CallFrame(0), Designator(V) {}
|
||||||
|
|
||||||
CallStackFrame *getLValueFrame() const {
|
CallStackFrame *getLValueFrame() const {
|
||||||
assert(getKind() == LValue);
|
assert(getKind() == LValue);
|
||||||
@ -336,15 +362,37 @@ static bool IsGlobalLValue(const Expr* E) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that this reference or pointer core constant expression is a valid
|
||||||
|
/// value for a constant expression. Type T should be either LValue or CCValue.
|
||||||
|
template<typename T>
|
||||||
|
static bool CheckLValueConstantExpression(const T &LVal, APValue &Value) {
|
||||||
|
if (!IsGlobalLValue(LVal.getLValueBase()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const SubobjectDesignator &Designator = LVal.getLValueDesignator();
|
||||||
|
// A constant expression must refer to an object or be a null pointer.
|
||||||
|
if (Designator.Invalid || Designator.OnePastTheEnd ||
|
||||||
|
(!LVal.getLValueBase() && !Designator.Entries.empty())) {
|
||||||
|
// FIXME: Check for out-of-bounds array indices.
|
||||||
|
// FIXME: This is not a constant expression.
|
||||||
|
Value = APValue(LVal.getLValueBase(), LVal.getLValueOffset(),
|
||||||
|
APValue::NoLValuePath());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value = APValue(LVal.getLValueBase(), LVal.getLValueOffset(),
|
||||||
|
Designator.Entries);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Check that this core constant expression value is a valid value for a
|
/// Check that this core constant expression value is a valid value for a
|
||||||
/// constant expression, and if it is, produce the corresponding constant value.
|
/// constant expression, and if it is, produce the corresponding constant value.
|
||||||
static bool CheckConstantExpression(const CCValue &CCValue, APValue &Value) {
|
static bool CheckConstantExpression(const CCValue &CCValue, APValue &Value) {
|
||||||
if (CCValue.isLValue() && !IsGlobalLValue(CCValue.getLValueBase()))
|
if (!CCValue.isLValue()) {
|
||||||
return false;
|
Value = CCValue;
|
||||||
|
return true;
|
||||||
// Slice off the extra bits.
|
}
|
||||||
Value = CCValue;
|
return CheckLValueConstantExpression(CCValue, Value);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ValueDecl *GetLValueBaseDecl(const LValue &LVal) {
|
const ValueDecl *GetLValueBaseDecl(const LValue &LVal) {
|
||||||
@ -595,7 +643,7 @@ bool HandleLValueToRValueConversion(EvalInfo &Info, QualType Type,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
assert(Type->isIntegerType() && "string element not integer type");
|
assert(Type->isIntegerType() && "string element not integer type");
|
||||||
uint64_t Index = Designator.Entries[0].Index;
|
uint64_t Index = Designator.Entries[0].ArrayIndex;
|
||||||
if (Index > S->getLength())
|
if (Index > S->getLength())
|
||||||
return false;
|
return false;
|
||||||
APSInt Value(S->getCharByteWidth() * Info.Ctx.getCharWidth(),
|
APSInt Value(S->getCharByteWidth() * Info.Ctx.getCharWidth(),
|
||||||
@ -1954,7 +2002,7 @@ static bool HasSameBase(const LValue &A, const LValue &B) {
|
|||||||
if (!ADecl)
|
if (!ADecl)
|
||||||
return false;
|
return false;
|
||||||
const Decl *BDecl = GetLValueBaseDecl(B);
|
const Decl *BDecl = GetLValueBaseDecl(B);
|
||||||
if (ADecl != BDecl)
|
if (!BDecl || ADecl->getCanonicalDecl() != BDecl->getCanonicalDecl())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3293,24 +3341,8 @@ bool Expr::EvaluateAsLValue(EvalResult &Result, const ASTContext &Ctx) const {
|
|||||||
EvalInfo Info(Ctx, Result);
|
EvalInfo Info(Ctx, Result);
|
||||||
|
|
||||||
LValue LV;
|
LValue LV;
|
||||||
if (EvaluateLValue(this, LV, Info) && !Result.HasSideEffects &&
|
return EvaluateLValue(this, LV, Info) && !Result.HasSideEffects &&
|
||||||
IsGlobalLValue(LV.Base)) {
|
CheckLValueConstantExpression(LV, Result.Val);
|
||||||
Result.Val = APValue(LV.Base, LV.Offset);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Expr::EvaluateAsAnyLValue(EvalResult &Result,
|
|
||||||
const ASTContext &Ctx) const {
|
|
||||||
EvalInfo Info(Ctx, Result);
|
|
||||||
|
|
||||||
LValue LV;
|
|
||||||
if (EvaluateLValue(this, LV, Info)) {
|
|
||||||
Result.Val = APValue(LV.Base, LV.Offset);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// isEvaluatable - Call EvaluateAsRValue to see if this expression can be
|
/// isEvaluatable - Call EvaluateAsRValue to see if this expression can be
|
||||||
|
@ -261,4 +261,22 @@ constexpr char c2 = "nasty index"[12]; // expected-error {{must be initialized b
|
|||||||
constexpr char c3 = "negative index"[-1]; // expected-error {{must be initialized by a constant expression}} expected-warning {{indexes before the beginning}}
|
constexpr char c3 = "negative index"[-1]; // expected-error {{must be initialized by a constant expression}} expected-warning {{indexes before the beginning}}
|
||||||
constexpr char c4 = ((char*)(int*)"no reinterpret_casts allowed")[14]; // expected-error {{must be initialized by a constant expression}}
|
constexpr char c4 = ((char*)(int*)"no reinterpret_casts allowed")[14]; // expected-error {{must be initialized by a constant expression}}
|
||||||
|
|
||||||
|
constexpr const char *p = "test" + 2;
|
||||||
|
static_assert_fold(*p == 's', "");
|
||||||
|
|
||||||
|
constexpr const char *max_iter(const char *a, const char *b) {
|
||||||
|
return *a < *b ? b : a;
|
||||||
|
}
|
||||||
|
constexpr const char *max_element(const char *a, const char *b) {
|
||||||
|
return (a+1 >= b) ? a : max_iter(a, max_element(a+1, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const char *begin(const char (&arr)[45]) { return arr; }
|
||||||
|
constexpr const char *end(const char (&arr)[45]) { return arr + 45; }
|
||||||
|
|
||||||
|
constexpr char str[] = "the quick brown fox jumped over the lazy dog";
|
||||||
|
constexpr const char *max = max_element(begin(str), end(str));
|
||||||
|
static_assert_fold(*max == 'z', "");
|
||||||
|
static_assert_fold(max == str + 38, "");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user