C ++, deklarasi variabel dalam ekspresi 'if'

114

Apa yang terjadi di sini?

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.
}

Bagian 6.4.3 dalam standar 2003 menjelaskan bagaimana variabel yang dideklarasikan dalam kondisi pernyataan pemilihan memiliki ruang lingkup yang meluas hingga akhir substrat yang dikontrol oleh kondisi tersebut. Tapi saya tidak melihat di mana dikatakan apa pun tentang tidak dapat menempatkan tanda kurung di sekitar deklarasi, juga tidak mengatakan apa pun tentang hanya satu deklarasi per kondisi.

Batasan ini mengganggu bahkan dalam kasus di mana hanya satu deklarasi dalam kondisi yang diperlukan. Pertimbangkan ini.

bool a = false, b = true;

if(bool x = a || b)
{

}

Jika saya ingin memasukkan 'if "-body scope dengan x disetel ke false maka deklarasi membutuhkan tanda kurung (karena operator penugasan memiliki prioritas lebih rendah daripada logika OR), tetapi karena tanda kurung tidak dapat digunakan, diperlukan deklarasi x di luar tubuh, membocorkan deklarasi itu ke lingkup yang lebih besar daripada yang diinginkan. Jelas contoh ini sepele tetapi kasus yang lebih realistis adalah kasus di mana a dan b adalah fungsi yang mengembalikan nilai yang perlu diuji

Jadi, apakah yang ingin saya lakukan tidak sesuai dengan standar, atau apakah kompiler saya hanya merusak bola saya (VS2008)?

Neutrino
sumber
6
"Jika saya ingin memasukkan loop dengan" <- contoh Anda miliki if. ifbukan sebuah loop, ini adalah sebuah kondisional.
crashmstr
2
@crashmstr: true, tetapi ketentuan untuk while sama seperti untuk if.
Mike Seymour
2
Tidak bisakah ini dilakukan dengan operator koma? Maksudku: if (int a = foo(), int b = bar(), a && b)? Jika operator koma tidak kelebihan beban, standar mengatakan bahwa ekspresi dievaluasi dari kiri ke kanan, dan nilai hasil adalah ekspresi terakhir. Ia bekerja dengan forinisialisasi loop, mengapa tidak di sini?
Archie
@Archie: Saya baru saja mencoba ini, saya tidak bisa membuatnya bekerja. Mungkin Anda bisa memberikan contoh kerja?
James Johnston
@ JamesJohnston: Saya baru saja mencoba, dan sepertinya tidak berhasil. Ide itu muncul begitu saja dari atas kepala saya, saya disarankan oleh cara ifkerjanya, dan sepertinya anggapan itu salah.
Archie

Jawaban:

63

Pada C ++ 17 apa yang Anda coba lakukan akhirnya mungkin :

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

Perhatikan penggunaan dari ;alih-alih ,untuk memisahkan deklarasi dan kondisi sebenarnya.

fwyzard.dll
sumber
23
Bagus! Saya selalu curiga saya berada di depan waktu saya.
Neutrino
106

Saya pikir Anda sudah mengisyaratkan masalah ini. Apa yang harus dilakukan kompilator dengan kode ini?

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

Operator "&&" adalah logika hubungan pendek AND. Artinya jika bagian pertama (1==0)ternyata salah, maka bagian kedua (bool a = false)tidak perlu dievaluasi karena sudah diketahui bahwa jawaban akhirnya salah. Jika (bool a = false)tidak dievaluasi, lalu apa yang harus dilakukan dengan kode nanti a? Akankah kita tidak menginisialisasi variabel dan membiarkannya tidak terdefinisi? Apakah kita akan menginisialisasinya ke default? Bagaimana jika tipe datanya adalah kelas dan melakukan ini memiliki efek samping yang tidak diinginkan? Bagaimana jika alih-alih boolAnda menggunakan kelas dan tidak memiliki konstruktor default sehingga pengguna harus memberikan parameter - lalu apa yang harus kita lakukan?

Berikut contoh lainnya:

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?

Sepertinya batasan yang Anda temukan tampaknya masuk akal - ini mencegah terjadinya ambiguitas semacam ini.

James Johnston
sumber
1
Poin yang bagus. Anda mungkin ingin menyebutkan hubungan arus pendek secara eksplisit, jika OP atau orang lain tidak terbiasa dengannya.
Chris Cooper
7
Saya tidak memikirkan itu. Meskipun dalam contoh yang Anda berikan, hubung singkat mencegah ruang lingkup pernyataan bersyarat dimasukkan, dalam hal ini bagian dari ekspresi yang menyatakan variabel tidak sedang diproses bukanlah masalah, karena ruang lingkupnya terbatas pada pernyataan bersyarat. Dalam kasus mana tidak akan lebih baik jika kompilator memunculkan kesalahan hanya dalam kasus di mana ada kemungkinan cakupan pernyataan bersyarat yang dimasukkan ketika bagian dari ekspresi yang menyatakan variabel tidak diproses? Yang tidak terjadi pada contoh yang saya berikan.
Neutrino
@Neutrino Pada pandangan pertama Anda ide terdengar seperti masalah SAT, yang tidak mudah untuk dipecahkan, setidaknya dalam kasus umum.
Christian Rau
5
Apa yang Anda jelaskan tentang semua masalah dengan memiliki beberapa deklarasi variabel dalam kondisi if dan fakta bahwa Anda hanya dapat menggunakannya secara terbatas membuat saya bertanya-tanya mengapa deklarasi semacam ini diperkenalkan di tempat pertama. Saya tidak pernah merasa perlu untuk sintaks seperti itu sebelum saya melihatnya di beberapa contoh kode. Saya menemukan sintaks ini agak kikuk dan saya pikir mendeklarasikan variabel sebelum blok if jauh lebih mudah dibaca. Jika Anda benar-benar perlu membatasi ruang lingkup variabel itu, Anda dapat meletakkan blok tambahan di sekitar blok if. Saya tidak pernah menggunakan sintaks ini.
Giorgio
2
Secara pribadi saya pikir akan agak elegan untuk dapat membatasi ruang lingkup variabel yang Anda gunakan dengan tepat cakupan blok pernyataan yang perlu menggunakannya, tanpa harus menggunakan tindakan jelek seperti kawat gigi pelingkupan tambahan bersarang.
Neutrino
96

Kondisi dalam pernyataan ifatau whiledapat berupa ekspresi , atau deklarasi variabel tunggal (dengan inisialisasi).

Contoh kedua dan ketiga Anda bukanlah ekspresi yang valid, maupun deklarasi yang valid, karena deklarasi tidak bisa menjadi bagian dari ekspresi. Meskipun akan berguna untuk dapat menulis kode seperti contoh ketiga Anda, ini akan membutuhkan perubahan signifikan pada sintaks bahasa.

Saya tidak melihat di mana dikatakan apa pun tentang tidak dapat menempatkan tanda kurung di sekitar deklarasi, juga tidak mengatakan apa pun tentang hanya satu deklarasi per kondisi.

Spesifikasi sintaks pada 6.4 / 1 memberikan kondisi berikut:

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

menentukan satu deklarasi, tanpa tanda kurung atau hiasan lainnya.

Mike Seymour
sumber
3
Apakah ada alasan atau latar belakang untuk ini?
Tomáš Zato - Kembalikan Monica
23

Jika Anda ingin memasukkan variabel dalam cakupan yang lebih sempit, Anda selalu dapat menggunakan tambahan { }

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

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope
crashmstr
sumber
5
+1. Selain itu, saya akan memindahkan deklarasi x ke blok sekitarnya: mengapa harus berstatus khusus wrt a dan b?
Giorgio
1
Jelas, tetapi tidak meyakinkan: Hal yang sama dapat dikatakan untuk variabel loop biasa. (Memang, kebutuhan untuk ruang lingkup variabel terbatas jauh lebih umum dalam loop.)
Peter - Reinstate Monica
18

Bagian terakhir sudah berfungsi, Anda hanya perlu menulisnya sedikit berbeda:

if (int a = Func1())
{
   if (int b = Func2())
   {
        // do stuff with a and b
   }
}
Bo Persson
sumber
2

Berikut adalah solusi buruk menggunakan perulangan (jika kedua variabel adalah bilangan bulat):

#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;
}

Tetapi ini akan membingungkan programmer lain dan ini adalah kode yang agak buruk, jadi tidak disarankan.

{}Blok penutup sederhana (seperti yang telah direkomendasikan) jauh lebih mudah dibaca:

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

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

Satu hal yang perlu diperhatikan, juga adalah ekspresi di dalam blok if yang lebih besar

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

tidak selalu dijamin untuk dievaluasi dengan cara kiri-ke-kanan. Satu bug yang agak halus yang saya hadapi pada hari itu berkaitan dengan fakta bahwa kompilator sebenarnya menguji kanan-ke-kiri alih-alih kiri-ke-kanan.

DukeBrymin
sumber
5
Namun, saat ini, C99 mengamanatkan bahwa && dan || dievaluasi dari kiri ke kanan.
b0fh
2
Saya pikir evaluasi argumen kanan-ke-kiri tidak pernah mungkin karena logika sirkuit pendek. Itu selalu digunakan untuk hal-hal seperti tes untuk pointer dan pointee dalam ekspresi tunggal, seperti if(p && p->str && *p->str) .... Kanan-ke kiri akan mematikan dan tidak terselubung. Dengan operator lain - bahkan penugasan! - dan argumen pemanggilan fungsi Anda benar, tetapi tidak dengan operator sirkuit pendek.
Peter - Kembalikan Monica
1

Dengan sedikit keajaiban templat Anda dapat mengatasi masalah tidak dapat mendeklarasikan banyak variabel:

#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;
}

(Perhatikan, ini menghilangkan evaluasi korsleting.)

BCS
sumber
Saya kira itu hilang (atau kendor?)
Peter - Reinstate Monica
5
Saya pikir maksud OP adalah singkatnya kode, kejelasan, dan pemeliharaan. Anda mengusulkan "solusi" melakukan yang sebaliknya.
Dženan