विषय पर बढ़ें

💡 सारांश:C++ में एक फ़ंक्शन से कई मान लौटाने के सभी तरीके — pair, tuple, struct, tie, callbacks, out parameters — और C++23 का std::expected बिना exceptions के value-or-error रिटर्न कैसे लिखता है।

अंतिम अद्यतन:2026-10-24

C++ multiple return values लिखने के तरीके

आज एक और क्लासिक interview सवाल: फ़ंक्शन कैसे कई परिणाम लौटाए? आप कितने तरीके सोच सकते हैं?

दरअसल यह सवाल बहुत सारे knowledge points जाँचता है, जिनमें शामिल हैं:

  • पॉइंटर
  • रेफ़रेंस
  • lambda
  • functional
  • tie
  • tuple
  • struct bingding
  • template deduce
  • आदि

यह लेख आपको अलग-अलग तरीकों से यह प्रश्न हल करके दिखाएगा, सबसे पहले हम इस प्रश्न को बदलते हैं, a / b का उपयोग करके (नीचे dividend 0 होने की स्थिति नहीं मानी) quotient और remainder लौटाते हैं। सरल शब्दों में, एक फ़ंक्शन quotient और remainder लौटाने के कितने तरीके हैं?

अब मुख्य विषय में आते हैं, नोट: इस सेक्शन का सारा कोड पहली बार Knowledge Planet पर जारी हुआ है, रुचि हो तो नीचे का QR कोड स्कैन करें।

1. पॉइंटर और रेफ़रेंस

इंटरफ़ेस बदलकर दो वेरिएबल जोड़ें, पॉइंटर और रेफ़रेंस का उपयोग करें।

void divideWithReferences(int dividend, int divisor, int& quotient, int& remainder) {
  quotient = dividend / divisor;
  remainder = dividend % divisor;
}

void divideWithPointers(int dividend, int divisor, int* quotient, int* remainder) {
  if (quotient) *quotient = dividend / divisor;
  if (remainder) *remainder = dividend % divisor;
}

इस तरीके की कमी साफ़ दिखती है, जब दो से ज़्यादा return values हों, तो इंटरफ़ेस बहुत लंबा हो जाता है। इसके अलावा, अगर interviewer फ़ंक्शन इंटरफ़ेस बदलने की अनुमति न दे, तो यह तरीका उपयोग नहीं हो सकता।

2. Tuple + Tie

Tuple और tie का combination फ़ंक्शन के कई वैल्यू लौटाने का संक्षिप्त तरीका देता है। std::tie का उपयोग करके हम tuple के एलिमेंट्स को अलग-अलग वेरिएबल्स में unpack कर सकते हैं, जिससे कोड की स्पष्टता बढ़ती है।

यह तरीका standard answers में से एक है, काफ़ी सरल और साफ़, अगर return values दो हों तो pair भी चल सकता है।

std::tuple<int, int> divide(int dividend, int divisor) {
    return std::make_tuple(dividend / divisor, dividend % divisor);
}


std::tie(quotient, remainder) = divide(14, 3);
std::cout << quotient << ", " << remainder << std::endl;

3. Struct Binding

ऊपर वाले को बदलें, C++17 के structured binding ने multi-value return का कोड और सरल कर दिया। auto और structured binding का उपयोग करके कोड ज़्यादा सहज और समझने योग्य बनता है।

अगर interviewer आपसे ऊपर के तरीके में और सुधार माँगे, तो यह नीचे वाला कोड उदाहरण बन सकता है, structured binding का उपयोग करके कोड ज़्यादा एलिगेंट बनाया जा सकता है।

auto divide(int dividend, int divisor) {
  struct result {
    int quotient;
    int remainder;
  };
  return result{dividend / divisor, dividend % divisor};
}
auto [quotient, remainder] = divide(14, 3);

4. फ़ंक्शन Callback

रोज़मर्रा के प्रोजेक्ट्स में, ख़ासकर कुछ ओपन-सोर्स प्रोजेक्ट्स में, callback एक आम तरीका है, return values को संभालने वाला callback पास करके, यूज़र को अपने हिसाब से संभालने देना, इस तरह कई वैल्यू लौटाना हासिल होता है और ज़्यादा लचीला कोड स्ट्रक्चर मिलता है। यह asynchronous programming और event handling जैसे केस के लिए बहुत उपयोगी है।

void divide(int dividend, int divisor, std::function<void(int, int)> callback) {
  callback(dividend / divisor, dividend % divisor);
}

5. कस्टम Out

कस्टम out एक स्ट्रक्चर में wrap करने का तरीका है, जिसमें output पैरामीटर को स्ट्रक्चर का मेंबर बनाया जाता है। यह तरीका कोड की पठनीयता बढ़ाता है, ख़ासकर ऐसे फ़ंक्शनों के लिए जिन्हें कई वैल्यू लौटानी हों।

template <class T>
struct out {
  std::function<void(T)> target;

  out(T* t)
      : target([t](T&& in) {
          if (t) *t = std::move(in);
        }) {}

  template <class... Args>
  void emplace(Args&&... args) {
    target(T(std::forward<Args>(args)...));
  }

  template <class X>
  void operator=(X&& x) {
    emplace(std::forward<X>(x));
  }

  template <class... Args>
  void operator()(Args&&... args) {
    emplace(std::forward<Args>(args)...);
  }
};

void divide(int dividend, int divisor, out<int>& quotient_out, out<int>& remainder_out) {
  quotient_out.emplace(dividend / divisor);
  remainder_out.emplace(dividend % divisor);
}

6. टेम्पलेट deduction

C++ का template deduction डेवलपर्स को ज़्यादा लचीला और संक्षिप्त कोड लिखने का तरीका देता है। template deduction के ज़रिए हम अलग-अलग टाइप का डेटा संभाल सकते हैं, टाइप स्पष्ट रूप से बताए बिना।

template <typename T1, typename T2>
struct many {
  T1 quotient;
  T2 remainder;
};

template <class T1, class T2>
many(T1, T2) -> many<T1, T2>;

many<int, int> divide(int dividend, int divisor) {
  return many{
      dividend / divisor,
      dividend % divisor,
  };
}

auto [quotient, remainder] = divide(14, 3);

कुल मिलाकर, C++ multi-value return के कई तरीके देता है, हर तरीके के अपने उपयुक्त केस हैं। उपयुक्त तरीके का चुनावख़ास ज़रूरतों पर निर्भर है, डेवलपर कोड स्ट्रक्चर और पठनीयता के हिसाब से इन features का लचीले तरीके से उपयोग कर सकते हैं।

C++23 std::expected: बिना exceptions के value या error

ऊपर की हर तकनीक मान लौटाती है — पर असली कोड में सबसे आम "दूसरा रिटर्न वैल्यू" ग़लती (error) होती है, और इनमें से कोई कंटेनर यह बात ज़ाहिर नहीं करता। std::pair कंपाइल हो जाता है, पर उस bool को नज़रअंदाज़ करने से कोई नहीं रोकता। Exceptions यह कह देते हैं, पर कीमत पर: control flow उलट-पुलट हो जाता है और signature में failure का रास्ता दिखता ही नहीं।

C++23 का std::expected यह अंतर भरता है। यह या तो मान रखता है या ग़लती; बिना जाँचे error तक पहुँचा ही नहीं जा सकता; और monadic helpers सुखद रास्ते को एक ज़ंजीर में जोड़ देते हैं:

#include <charconv>
#include <expected>
#include <string>
#include <string_view>

std::expected<int, std::string> parse_int(std::string_view sv) {
    int value{};
    auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), value);
    if (ec != std::errc{}) {
        return std::unexpected("not a number: " + std::string(sv));
    }
    return value;
}

auto r = parse_int("42");
if (r) { use(*r); } else { log(r.error()); }
// monadic chaining: parse_int(s).and_then(validate).transform(square)

पड़ोसियों में इसकी जगह: std::optional लगभग expected है — असफलता को कारण नहीं चाहिए तो वही; std::variant वही सामान ढोता है पर सफलता/असफलता का अर्थ नहीं; expected वह संस्करण है जो मंशा साफ़ कहता है। कंपाइलर सपोर्ट: GCC 13+, Clang 16+, MSVC VS 2022 17.3 से।

अक्सर पूछे जाने वाले प्रश्न

std::expected, std::optional और std::variant में कौन?

जब "कोई मान नहीं" के लिए कारण न चाहिए तो optional। जब दोनों विकल्प समान रुतबे के जायज़ मान हों तो variant। जब दूसरा स्थान एक ऐसी ग़लती हो जिसे caller को साफ़-साफ़ संभालना चाहिए तो expected।

क्या std::expected मेमोरी आवंटित करता है?

नहीं। optional और variant की तरह यह मान या ग़लती अपने ही भंडार में inline रखता है। heap allocation नहीं होता; sizeof उस पर निर्भर करता है T, E और alignment पर।

std::expected की जगह exceptions कब इस्तेमाल करें?

सचमुच असाधारण स्थितियों — निर्माण विफलता, कई परतों को पार करती ग़लतियाँ — के लिए exceptions बरक़रार रखें जहाँ stack unwinding सार्थक हो। parsing और validation जैसी अपेक्षित, call site के पास रिकवर होने वाली विफलताओं के लिए expected।

संबंधित विषय

टिप्पणियाँ