if constexpr - เพราะเหตุใดคำสั่งที่ถูกยกเลิกจะถูกตรวจสอบอย่างสมบูรณ์?


14

ฉันยุ่งกับ c ++ 20 consteval ใน GCC 10 และเขียนรหัสนี้

#include <optional>
#include <tuple>
#include <iostream>

template <std::size_t N, typename Predicate, typename Tuple>
consteval std::optional<std::size_t> find_if_impl(Predicate&& pred,
                                                  Tuple&& t) noexcept {
  constexpr std::size_t I = std::tuple_size_v<std::decay_t<decltype(t)>> - N;

  if constexpr (N == 0u) {
    return std::nullopt;
  } else {
    return pred(std::get<I>(t))
               ? std::make_optional(I)
               : find_if_impl<N - 1u>(std::forward<decltype(pred)>(pred),
                                      std::forward<decltype(t)>(t));
  }
}

template <typename Predicate, typename Tuple>
consteval std::optional<std::size_t> find_if(Predicate&& pred,
                                             Tuple&& t) noexcept {
  return find_if_impl<std::tuple_size_v<std::decay_t<decltype(t)>>>(
      std::forward<decltype(pred)>(pred), std::forward<decltype(t)>(t));
}

constexpr auto is_integral = [](auto&& x) noexcept {
    return std::is_integral_v<std::decay_t<decltype(x)>>;
};


int main() {
    auto t0 = std::make_tuple(9, 1.f, 2.f);
    constexpr auto i = find_if(is_integral, t0);
    if constexpr(i.has_value()) {
        std::cout << std::get<i.value()>(t0) << std::endl;
    }
}

ซึ่งควรจะทำงานเหมือนอัลกอริธึม STL ค้นหา แต่บน tuples และแทนที่จะส่งคืนตัววนซ้ำมันจะส่งคืนดัชนีทางเลือกโดยยึดตามคำกริยาเวลารวบรวม ตอนนี้รหัสนี้รวบรวมได้ดีและมันพิมพ์ออกมา

9

แต่ถ้า tuple ไม่มีองค์ประกอบที่เป็นส่วนประกอบสำคัญโปรแกรมจะไม่คอมไพล์เนื่องจาก i.value () ยังคงถูกเรียกใช้บนตัวเลือกที่ว่างเปล่า ทำไมถึงเป็นอย่างนั้น?



@AndyG ที่ไม่สามารถแก้ไขได้ใช่ไหม x)
Yamahari

คำตอบ:


11

นี่เป็นวิธีที่constexpr ถ้าทำงาน หากเราตรวจสอบ[stmt.if] / 2

ถ้าคำสั่ง if เป็นของรูปแบบถ้า constexpr ค่าของเงื่อนไขจะเป็นการแสดงออกคงที่แปลงบริบทของประเภทบูล; แบบฟอร์มนี้เรียกว่า constexpr if statement หากค่าของเงื่อนไขที่ถูกแปลงเป็นเท็จสถานะย่อยแรกคือคำสั่งที่ถูกทิ้งมิฉะนั้นสถานะย่อยที่สองถ้ามีจะเป็นคำสั่งที่ถูกทิ้ง ในระหว่างการสร้างอินสแตนซ์ของเอนทิตีที่ปิดล้อม ([temp.pre]) หากเงื่อนไขไม่ได้ขึ้นอยู่กับค่าหลังจากอินสแตนซ์ของมันสถานะย่อยที่ถูกทิ้ง (ถ้ามี) จะไม่ถูกสร้าง [ ... ]

ฉันเน้น

ดังนั้นเราจะเห็นได้ว่าเราไม่ได้ประเมินเฉพาะนิพจน์ที่ถูกทิ้งหากเราอยู่ในเทมเพลตและถ้าเงื่อนไขนั้นขึ้นอยู่กับค่า mainไม่ใช่เทมเพลตฟังก์ชันดังนั้นเนื้อความของคำสั่ง if ยังคงถูกตรวจสอบโดยคอมไพเลอร์เพื่อความถูกต้อง

Cppreference ยังกล่าวในส่วนที่เกี่ยวกับ constexpr ด้วย:

หากกลุ่มข้อความถ้าคำสั่งปรากฏภายในเอนทิตี templated และหากเงื่อนไขไม่ขึ้นอยู่กับค่าหลังจากการสร้างอินสแตนซ์คำสั่งที่ถูกยกเลิกจะไม่ถูกสร้างอินสแตนซ์เมื่อเทมเพลตล้อมรอบถูกสร้างอินสแตนซ์

template<typename T, typename ... Rest>
void g(T&& p, Rest&& ...rs) {
    // ... handle p
    if constexpr (sizeof...(rs) > 0)
        g(rs...); // never instantiated with an empty argument list.
}

นอกเทมเพลตคำสั่งที่ถูกยกเลิกจะถูกตรวจสอบอย่างสมบูรณ์ ถ้า constexpr ไม่ได้ใช้แทน #if preprocessing directive:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}

คุณรู้เหตุผลของสิ่งนี้หรือไม่? ดูเหมือนว่านี่จะเป็นแบบที่ดีถ้า constexpr นอกจากนี้วิธีการแก้ปัญหาจะเป็นเช่นห่อในเทมเพลตอย่างใด?
ยามาฮาริ

@Yamahari เนื่องจากเทมเพลต C ++ มีทั้งโครงสร้างที่มากขึ้นและน้อยลงกว่าที่คุณต้องการ และใช่ห่อไว้ในเทมเพลต (หรือเขียนเหมือนi.value_or(0))
Barry

2
@ ยามาฮาริใช่วิธีแก้ปัญหาคือการใส่รหัสในเทมเพลตฟังก์ชั่น ฉันไม่รู้ว่าทำไม นั่นอาจเป็นคำถามที่ดีที่จะถาม
NathanOliver

@Barry value_or (0) ทำงานได้ดี แต่ในกรณีที่ tuple มีขนาด 0
Yamahari

@ ยามาฮาริใช่ ... ไม่ใช่คำแนะนำที่ดีในส่วนของฉัน
Barry
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.