C ++ การประกาศตัวแปรในนิพจน์ 'if'


114

เกิดอะไรขึ้นที่นี่?

if(int a = Func1())
{
    // Works.
}

if((int a = Func1()))
{
    // Fails to compile.
}

if((int a = Func1())
    && (int b = Func2()))
)
{
    // Do stuff with a and b.
    // This is what I'd really like to be able to do.
}

ส่วน 6.4.3 ในมาตรฐาน 2003 อธิบายว่าตัวแปรที่ประกาศในเงื่อนไขคำสั่งการเลือกมีขอบเขตที่ขยายไปถึงจุดสิ้นสุดของส่วนย่อยที่ควบคุมโดยเงื่อนไขอย่างไร แต่ฉันไม่เห็นว่ามันพูดอะไรเกี่ยวกับการไม่สามารถใส่วงเล็บรอบคำประกาศหรือไม่พูดอะไรเกี่ยวกับการประกาศเพียงครั้งเดียวต่อเงื่อนไข

ข้อ จำกัด นี้น่ารำคาญแม้ในกรณีที่จำเป็นต้องมีการประกาศเพียงครั้งเดียวในเงื่อนไข พิจารณาสิ่งนี้.

bool a = false, b = true;

if(bool x = a || b)
{

}

หากฉันต้องการป้อนขอบเขต 'if' -body โดยตั้งค่า x เป็นเท็จการประกาศจำเป็นต้องมีวงเล็บ (เนื่องจากตัวดำเนินการกำหนดมีลำดับความสำคัญต่ำกว่าตรรกะ OR) แต่เนื่องจากไม่สามารถใช้วงเล็บได้จึงต้องมีการประกาศ x ภายนอก เนื้อความรั่วการประกาศนั้นไปยังขอบเขตที่ใหญ่กว่าที่ต้องการเห็นได้ชัดว่าตัวอย่างนี้เป็นเรื่องเล็กน้อย แต่กรณีที่เป็นจริงกว่านั้นน่าจะเป็นกรณีที่ a และ b เป็นฟังก์ชันที่ส่งคืนค่าที่ต้องได้รับการทดสอบ

ฉันต้องการทำสิ่งที่ไม่เป็นไปตามมาตรฐานหรือไม่หรือคอมไพเลอร์ของฉันแค่ทำลายลูกบอล (VS2008)


6
"ถ้าผมต้องการที่จะเข้าสู่วงด้วย" <- ifตัวอย่างของคุณมี ifไม่ใช่การวนซ้ำ แต่เป็นเงื่อนไข
crashmstr

2
@crashmstr: จริง แต่เงื่อนไขสำหรับการเป็นเช่นเดียวกับwhile if
Mike Seymour

2
ไม่สามารถทำได้ด้วยตัวดำเนินการลูกน้ำ? ฉันหมายถึง: if (int a = foo(), int b = bar(), a && b)? ถ้าตัวดำเนินการลูกน้ำไม่โอเวอร์โหลดมาตรฐานจะบอกว่านิพจน์จะถูกประเมินจากซ้ายไปขวาและค่าผลลัพธ์คือนิพจน์สุดท้าย ทำงานร่วมกับการforเริ่มต้นลูปทำไมไม่มาที่นี่
Archie

@ Archie: ฉันเพิ่งลองสิ่งนี้ฉันไม่สามารถใช้งานได้ บางทีคุณอาจให้ตัวอย่างการทำงาน?
James Johnston

@JamesJohnston: ฉันเพิ่งลองใช้และดูเหมือนจะไม่ได้ผล ความคิดนั้นเพิ่งมาจากด้านบนของหัวของฉันฉันได้รับคำแนะนำจากวิธีการifทำงานและดูเหมือนว่าจะเป็นการสันนิษฐานที่ผิด
Archie

คำตอบ:


63

จาก C ++ 17 สิ่งที่คุณพยายามทำในที่สุดก็เป็นไปได้ :

if (int a = Func1(), b = Func2(); a && b)
{
    // Do stuff with a and b.
}

สังเกตการใช้;แทน,เพื่อแยกการประกาศกับสภาพจริง


23
ดี! ฉันมักจะสงสัยว่าฉันมาก่อนเวลาของฉัน
Neutrino

106

ฉันคิดว่าคุณได้บอกใบ้ปัญหาแล้ว คอมไพเลอร์ควรทำอย่างไรกับโค้ดนี้

if (!((1 == 0) && (bool a = false))) {
    // what is "a" initialized to?

ตัวดำเนินการ "&&" เป็นตรรกะลัดวงจร AND นั่นหมายความว่าหากส่วนแรก(1==0)กลายเป็นเท็จดังนั้นส่วนที่สอง(bool a = false)ไม่ควรได้รับการประเมินเนื่องจากเป็นที่ทราบกันดีอยู่แล้วว่าคำตอบสุดท้ายจะเป็นเท็จ หาก(bool a = false)ไม่ได้รับการประเมินแล้วจะทำอย่างไรกับโค้ดในภายหลังที่ใช้a? เราจะไม่เริ่มต้นตัวแปรและปล่อยไว้โดยไม่ได้กำหนดหรือไม่? เราจะเริ่มต้นเป็นค่าเริ่มต้นหรือไม่? จะเกิดอะไรขึ้นถ้าประเภทข้อมูลเป็นคลาสและการทำเช่นนี้มีผลข้างเคียงที่ไม่พึงปรารถนา? จะเกิดอะไรขึ้นถ้าboolคุณใช้คลาสและไม่มีตัวสร้างเริ่มต้นเพื่อให้ผู้ใช้ต้องระบุพารามิเตอร์ - เราจะทำอย่างไร?

นี่คือตัวอย่างอื่น:

class Test {
public:
    // note that no default constructor is provided and user MUST
    // provide some value for parameter "p"
    Test(int p);
}

if (!((1 == 0) && (Test a = Test(5)))) {
    // now what do we do?!  what is "a" set to?

ดูเหมือนว่าข้อ จำกัด ที่คุณพบนั้นดูสมเหตุสมผลดี - มันป้องกันไม่ให้ความคลุมเครือประเภทนี้เกิดขึ้น


1
จุดดี. คุณอาจต้องการพูดถึงการลัดวงจรอย่างชัดเจนในกรณีที่ OP หรือคนอื่นไม่คุ้นเคยกับมัน
Chris Cooper

7
ฉันไม่ได้คิดอย่างนั้น แม้ว่าในตัวอย่างที่คุณระบุการลัดวงจรจะป้องกันไม่ให้ป้อนขอบเขตของคำสั่งเงื่อนไข แต่ในกรณีนี้ส่วนของนิพจน์ที่ประกาศว่าตัวแปรไม่ได้รับการประมวลผลนั้นไม่ใช่ปัญหาเนื่องจากขอบเขตถูก จำกัด ไว้ที่คำสั่งเงื่อนไข ในกรณีใดจะไม่ดีไปกว่ากันถ้าคอมไพลเลอร์ยกข้อผิดพลาดเฉพาะในกรณีที่มีการป้อนขอบเขตของคำสั่งเงื่อนไขที่เป็นไปได้เมื่อส่วนหนึ่งของนิพจน์ที่ประกาศตัวแปรไม่ได้รับการประมวลผล ซึ่งไม่เป็นเช่นนั้นในตัวอย่างที่ฉันให้ไว้
Neutrino

@Neutrino เมื่อแรกเห็นคุณคิดว่าฟังดูคล้ายกับปัญหา SAT ซึ่งไม่ใช่เรื่องง่ายที่จะแก้ปัญหาอย่างน้อยก็ในกรณีทั่วไป
Christian Rau

5
สิ่งที่คุณอธิบายเกี่ยวกับปัญหาทั้งหมดเกี่ยวกับการประกาศตัวแปรหลายตัวในเงื่อนไข if และความจริงที่ว่าคุณสามารถใช้ได้ในวิธีที่ จำกัด เท่านั้นทำให้ฉันสงสัยว่าทำไมการประกาศแบบนี้จึงเกิดขึ้นบนโลกในตอนแรก ฉันไม่เคยรู้สึกว่าต้องการไวยากรณ์แบบนี้มาก่อนที่ฉันจะเห็นมันในตัวอย่างโค้ด ฉันพบว่าไวยากรณ์นี้ค่อนข้างเงอะงะและฉันคิดว่าการประกาศตัวแปรก่อนบล็อก if นั้นอ่านได้ง่ายกว่ามาก หากคุณต้องการ จำกัด ขอบเขตของตัวแปรนั้นจริงๆคุณสามารถวางบล็อกพิเศษไว้รอบ ๆ if block ได้ ฉันไม่เคยใช้ไวยากรณ์นี้
Giorgio

2
โดยส่วนตัวแล้วฉันคิดว่ามันค่อนข้างดีที่จะสามารถ จำกัด ขอบเขตของตัวแปรที่คุณใช้ให้ตรงกับขอบเขตของบล็อกคำสั่งที่ต้องการใช้โดยไม่ต้องใช้มาตรการที่น่าเกลียดเช่นวงเล็บปีกกาแบบซ้อนพิเศษ
Neutrino

96

เงื่อนไขในคำสั่งifหรือwhileอาจเป็นได้ทั้งนิพจน์หรือการประกาศตัวแปรเดียว(ด้วยการเริ่มต้น)

ตัวอย่างที่สองและสามของคุณไม่ใช่นิพจน์ที่ถูกต้องหรือการประกาศที่ถูกต้องเนื่องจากการประกาศไม่สามารถเป็นส่วนหนึ่งของนิพจน์ได้ แม้ว่าจะมีประโยชน์ในการเขียนโค้ดเหมือนตัวอย่างที่สามของคุณ แต่ก็จำเป็นต้องมีการเปลี่ยนแปลงที่สำคัญในไวยากรณ์ของภาษา

ฉันไม่เห็นว่ามันพูดอะไรเกี่ยวกับการไม่สามารถใส่วงเล็บรอบคำประกาศหรือไม่พูดอะไรเกี่ยวกับการประกาศเพียงครั้งเดียวต่อเงื่อนไข

ข้อกำหนดทางไวยากรณ์ใน 6.4 / 1 ให้เงื่อนไขต่อไปนี้:

condition:
    expression
    type-specifier-seq declarator = assignment-expression

ระบุคำประกาศเดียวโดยไม่มีวงเล็บหรือเครื่องประดับอื่น ๆ


3
มีเหตุผลหรือความเป็นมาของเรื่องนี้หรือไม่?
Tomáš Zato - คืนสถานะ Monica

23

หากคุณต้องการใส่ตัวแปรในขอบเขตที่แคบลงคุณสามารถใช้เพิ่มเติมได้ตลอดเวลา { }

//just use { and }
{
    bool a = false, b = true;

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope

5
+1. นอกจากนี้ฉันจะย้ายการประกาศ x ไปยังบล็อกโดยรอบ: ทำไมจึงควรมีสถานะพิเศษ wrt a และ b?
Giorgio

1
ชัดเจน แต่ไม่น่าเชื่อ: เช่นเดียวกันกับตัวแปรลูปธรรมดา (จริงอยู่ความต้องการขอบเขตตัวแปรที่ จำกัด นั้นพบได้บ่อยในลูป)
ปีเตอร์ - คืนสถานะโมนิกา


2

นี่เป็นวิธีแก้ปัญหาที่น่าเกลียดโดยใช้การวนซ้ำ (หากตัวแปรทั้งสองเป็นจำนวนเต็ม):

#include <iostream>

int func1()
{
    return 4;
}

int func2()
{
    return 23;
}

int main()
{
    for (int a = func1(), b = func2(), i = 0;
        i == 0 && a && b; i++)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }

    return 0;
}

แต่สิ่งนี้จะสร้างความสับสนให้กับโปรแกรมเมอร์คนอื่น ๆ และเป็นรหัสที่ไม่ดีจึงไม่แนะนำ

{}บล็อกปิดล้อมอย่างง่าย(ตามที่แนะนำแล้ว) อ่านง่ายกว่ามาก:

{
    int a = func1();
    int b = func2();

    if (a && b)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }
}

1

สิ่งหนึ่งที่ควรทราบก็คือนิพจน์ภายใน if-block ที่ใหญ่กว่า

if (!((1 == 0) && (bool a = false)))

ไม่จำเป็นต้องรับประกันว่าจะได้รับการประเมินจากซ้ายไปขวา ข้อผิดพลาดที่ค่อนข้างละเอียดอ่อนอย่างหนึ่งที่ฉันย้อนกลับไปในวันนั้นเกี่ยวข้องกับข้อเท็จจริงที่ว่าคอมไพเลอร์กำลังทดสอบจากขวาไปซ้ายแทนที่จะเป็นซ้ายไปขวา


5
อย่างไรก็ตามปัจจุบัน C99 กำหนดว่า && และ || จะถูกประเมินจากซ้ายไปขวา
b0fh

2
ฉันคิดว่าการประเมินจากขวาไปซ้ายของอาร์กิวเมนต์ไม่สามารถทำได้เนื่องจากตรรกะการลัดวงจร มันถูกใช้เสมอสำหรับสิ่งที่ต้องการทดสอบสำหรับตัวชี้และ pointee if(p && p->str && *p->str) ...ในการแสดงออกเดียวเช่น ขวาไปซ้ายน่าจะเป็นอันตรายและไม่ละเอียดอ่อน กับผู้ประกอบการรายอื่น - แม้กระทั่งการมอบหมายงาน! - และอาร์กิวเมนต์การเรียกใช้ฟังก์ชันคุณพูดถูก แต่ไม่ใช่กับตัวดำเนินการลัดวงจร
Peter - คืนสถานะ Monica

1

ด้วยเวทมนตร์แม่แบบเล็กน้อยคุณสามารถแก้ไขปัญหาที่ไม่สามารถประกาศตัวแปรหลายตัวได้:

#include <stdio.h>

template <class LHS, class RHS>
struct And_t {
  LHS lhs;
  RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs && b_rhs;
  }
};
template <class LHS, class RHS> 
And_t<LHS, RHS> And(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

template <class LHS, class RHS>
struct Or_t {
LHS lhs;
RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs || b_rhs;
  }
};
template <class LHS, class RHS> 
Or_t<LHS, RHS> Or(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

int main() {
  if (auto i = And(1, Or(0, 3))) {
    printf("%d %d %d\n", i.lhs, i.rhs.lhs, i.rhs.rhs);
  }
  return 0;
}

(หมายเหตุสิ่งนี้จะสูญเสียการประเมินการลัดวงจร)


ฉันคิดว่ามันสูญเสีย (หรือคลายตัว?)
ปีเตอร์ - คืนสถานะโมนิกา

5
ฉันคิดว่าเจตนาของ OP คือความกะทัดรัดความชัดเจนและการบำรุงรักษา คุณเสนอ "วิธีแก้ปัญหา" ในทางตรงกันข้าม
Dženan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.