I have two functions
int test3(int *vec, int *vec2, int n, int m)
{
    int sum = 0;
    for (int j = n; j < m; j++)
        sum += vec[vec2[j]] + vec[vec2[j] + 1];
    return sum;
}

int test4(std::vector<int> vec, std::vector<int> vec2, int n, int m)
{
    int sum = 0;
    for (int j = n; j < m; j++)
        sum += vec[vec2[j]] + vec[vec2[j] + 1];
    return sum;
}
Clang's result IR is following with -O1
define dso_local noundef i32 @test3(int*, int*, int, int)(ptr noundef readonly captures(none) %vec, ptr noundef readonly captures(none) %vec2, i32 noundef %n, i32 noundef %m) local_unnamed_addr {
entry:
  %cmp15 = icmp slt i32 %n, %m
  br i1 %cmp15, label %for.body.preheader, label %for.cond.cleanup

for.body.preheader:
  %0 = sext i32 %n to i64
  %wide.trip.count = sext i32 %m to i64
  br label %for.body

for.cond.cleanup:
  %sum.0.lcssa = phi i32 [ 0, %entry ], [ %add8, %for.body ]
  ret i32 %sum.0.lcssa

for.body:
  %indvars.iv = phi i64 [ %0, %for.body.preheader ], [ %indvars.iv.next, %for.body ]
  %sum.016 = phi i32 [ 0, %for.body.preheader ], [ %add8, %for.body ]
  %arrayidx = getelementptr inbounds i32, ptr %vec2, i64 %indvars.iv
  %1 = load i32, ptr %arrayidx, align 4
  %idxprom1 = sext i32 %1 to i64
  %arrayidx2 = getelementptr inbounds i32, ptr %vec, i64 %idxprom1
  %2 = load i32, ptr %arrayidx2, align 4
  %arrayidx6 = getelementptr i8, ptr %arrayidx2, i64 4
  %3 = load i32, ptr %arrayidx6, align 4
  %add7 = add i32 %2, %sum.016
  %add8 = add i32 %add7, %3
  %indvars.iv.next = add nsw i64 %indvars.iv, 1
  %exitcond.not = icmp eq i64 %indvars.iv.next, %wide.trip.count
  br i1 %exitcond.not, label %for.cond.cleanup, label %for.body
}

define dso_local noundef i32 @test4(std::vector<int, std::allocator<int>>, std::vector<int, std::allocator<int>>, int, int)(ptr noundef readonly captures(none) %vec, ptr noundef readonly captures(none) %vec2, i32 noundef %n, i32 noundef %m) local_unnamed_addr {
entry:
  %cmp16 = icmp slt i32 %n, %m
  br i1 %cmp16, label %for.body.lr.ph, label %for.cond.cleanup

for.body.lr.ph:
  %0 = load ptr, ptr %vec2, align 8
  %1 = load ptr, ptr %vec, align 8
  %invariant.gep = getelementptr i8, ptr %1, i64 4
  %2 = sext i32 %n to i64
  %wide.trip.count = sext i32 %m to i64
  br label %for.body

for.cond.cleanup:
  %sum.0.lcssa = phi i32 [ 0, %entry ], [ %add8, %for.body ]
  ret i32 %sum.0.lcssa

for.body:
  %indvars.iv = phi i64 [ %2, %for.body.lr.ph ], [ %indvars.iv.next, %for.body ]
  %sum.017 = phi i32 [ 0, %for.body.lr.ph ], [ %add8, %for.body ]
  %add.ptr.i = getelementptr inbounds nuw i32, ptr %0, i64 %indvars.iv
  %3 = load i32, ptr %add.ptr.i, align 4
  %conv1 = sext i32 %3 to i64
  %add.ptr.i13 = getelementptr inbounds nuw i32, ptr %1, i64 %conv1
  %4 = load i32, ptr %add.ptr.i13, align 4
  %5 = sext i32 %3 to i64
  %gep = getelementptr i32, ptr %invariant.gep, i64 %5
  %6 = load i32, ptr %gep, align 4
  %add7 = add i32 %4, %sum.017
  %add8 = add i32 %add7, %6
  %indvars.iv.next = add nsw i64 %indvars.iv, 1
  %exitcond.not = icmp eq i64 %indvars.iv.next, %wide.trip.count
  br i1 %exitcond.not, label %for.cond.cleanup, label %for.body
}

Why for pointer case, the getelementptr for vec[vec2[j]+1] is   %arrayidx20 = getelementptr i8, ptr %arrayidx15, i64 4, uses vec[vec2[j]]'s ptr offset 4, but for vector case, the getelementptr is %gep = getelementptr i32, ptr %invariant.gep, i64 %5, uses the invariant.gep that offset vec's ptr by 4