विषय पर बढ़ें

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

C++20 Concepts अवधारणा

C++20 ने concept पेश किया, जो टेम्प्लेट पैरामीटर (compile-समय मूल्यांकन) पर एक सेट के constraints हैं। आप इन्हें class templates और function templates पर उपयोग कर function overloading और specialization को नियंत्रित कर सकते हैं। कुछ फ़ायदों में शामिल हैं:

  • टेम्प्लेट पैरामीटर पर type constraints लागू करना
  • कोड की पठनीयता बढ़ाना (लंबे SFINAE कोड की जगह)
  • अधिक अनुकूल त्रुटि संदेश देना
  • उपयोग योग्य प्रकारों को सीमित कर अनजाने template instantiation से बचाना

एक सरल जोड़ (addition) का उदाहरण लेते हैं।

#include <iostream>

struct Foo {};

template <typename T>
T Add(T a, T b) {
  return a + b;
}

int main() {
  std::cout << Add(1, 2) << std::endl;
  Foo f1, f2;
  std::cout << Add(f1, f2) << std::endl;
  return 0;
}

Foo के लिए, addition समर्थित नहीं है, और साथ ही सीधे std::cout << भी नहीं कर सकते, इसलिए compile के समय ढेर सारी त्रुटियाँ आती हैं, जिनमें operator<< और operator+ शामिल हैं, लेकिन यह हमारी अपेक्षित त्रुटि संदेश नहीं है, हम चाहते हैं कि कंपाइलर हमें सबसे सहज त्रुटि संदेश दे, यानी: यह संरचना जुड़ सकती है या नहीं।

add.cc: In function 'int main()':
add.cc:13:13: error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'Foo')
   13 |   std::cout << Add(f1, f2) << std::endl;
      |   ~~~~~~~~~ ^~ ~~~~~~~~~~~
      |        |          |
      |        |          Foo
      |        std::ostream {aka std::basic_ostream<char>}
In file included from /usr/local/Cellar/gcc/13.2.0/include/c++/13/iostream:41,
                 from add.cc:1:
/usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:110:7: note: candidate: 'std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(__ostream_type& (*)(__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; __ostream_type = std::basic_ostream<char>]'
  110 |       operator<<(__ostream_type& (*__pf)(__ostream_type&))

/usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:801:5: note:   template argument deduction/substitution failed:
/usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream: In substitution of 'template<class _Ostream, class _Tp> _Ostream&& std::operator<<(_Ostream&&, const _Tp&) [with _Ostream = std::basic_ostream<char>&; _Tp = Foo]':
add.cc:13:26:   required from here
/usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:801:5: error: no type named 'type' in 'struct std::enable_if<false, void>'
add.cc: In instantiation of 'T Add(T, T) [with T = Foo]':
add.cc:13:19:   required from here
add.cc:7:12: error: no match for 'operator+' (operand types are 'Foo' and 'Foo')
    7 |   return a + b;
      |          ~~^~~

जब हम concept से लागू करते हैं:

template <typename T>
concept Addable = requires(T a, T b) { a + b; };

template <typename T>
  requires Addable<T>
T Add(T a, T b) {
  return a + b;
}

तो हमें हमारी परवाह की त्रुटि मिलती है:

add_concept.cc: In function 'int main()':
add_concept.cc:17:19: error: no matching function for call to 'Add(Foo&, Foo&)'
   17 |   std::cout << Add(f1, f2) << std::endl;
      |                ~~~^~~~~~~~
add_concept.cc:10:3: note: candidate: 'template<class T>  requires  Addable<T> T Add(T, T)'
   10 | T Add(T a, T b) {
      |   ^~~
add_concept.cc:10:3: note:   template argument deduction/substitution failed:
add_concept.cc:10:3: note: constraints not satisfied
add_concept.cc: In substitution of 'template<class T>  requires  Addable<T> T Add(T, T) [with T = Foo]':
add_concept.cc:17:19:   required from here
add_concept.cc:6:9:   required for the satisfaction of 'Addable<T>' [with T = Foo]
add_concept.cc:6:19:   in requirements with 'T a', 'T b' [with T = Foo]
add_concept.cc:6:42: note: the required expression '(a + b)' is invalid
    6 | concept Addable = requires(T a, T b) { a + b; };
      |                                        ~~^~~

नीचे, हम ऊपर वाले उदाहरण के साथ concept सिंटैक्स गहराई से सीखते हैं।

1.concept सिंटैक्स

सिंटैक्स:

template <template-parameter-list>
concept concept-name = constraint-expression;

हम वास्तविक उदाहरण से तुलना करते हैं, Addable concept-name है, constraint-expression है requires(T a, T b) { a + b; }।

template <typename T>
concept Addable = requires(T a, T b) { a + b; };

उपयोग का तरीका:

#include <concepts>

इस concept को कई जगह रखा जा सकता है, जैसे:

  • typename की जगह
  • requires के बाद
  • auto के पहले

1.1 typename की जगह

टेम्प्लेट पैरामीटर constraint देना, typename की जगह।

// template <typename T>  typename->Addable
template <Addable T>
T Add(T a, T b) {
  return a + b;
}

1.2 requires कीवर्ड

हम function template में requires कीवर्ड का उपयोग करते हैं। यह हमारे टेम्प्लेट T के जुड़ने योग्य होने तक पहुँच सकता है, यदि टेम्प्लेट addition संभाल सकता है, तो यह true लौटाएगा।

requires को टेम्प्लेट में रखा जा सकता है, और फ़ंक्शन के बाद भी, लेकिन class के बाद नहीं। साथ ही इसके दो रूप हैं:

  • requires शर्त

उदाहरण के लिए:

template <typename T>
  requires Addable<T>
T Add(T a, T b) { ... }
  • requires एक्सप्रेशन

उदाहरण के लिए:

template <typename T>
concept Addable = requires(T a, T b) { a + b; };

1.2.1 टेम्प्लेट में रखना

फ़ंक्शन:

template <typename T>
  requires Addable<T>
T Add(T a, T b) {
  return a + b;
}

क्लास:

template <typename T>
    requires Addable<T> 
class Bar {
 public:
  T Add(T a, T b) { return a + b; }
};

1.2.2 फ़ंक्शन का trailing Requires क्लॉज़

फ़ंक्शन:

template <typename T>
T Add(T a, T b)
  requires Addable<T>
{
  return a + b;
}

क्लास के लिए यह लेखन-शैली समर्थित नहीं है, त्रुटि मिलेगी: error: expected unqualified-id before 'requires' 28 | requires Addable

template <typename T>
class Bar requires Addable<T> 
{
 public:
  T Add(T a, T b) { return a + b; }
};

1.2.3 requires मूल उदाहरण

डेटाबेस के प्रकारों का उदाहरण लें, डेटाबेस में भिन्न प्रकार होते हैं, हम उन्हें इस तरह बाँटते हैं: null, binary, number आदि, हम पास किए गए प्रकार पर print ऑपरेशन चाहते हैं, इसलिए यह उदाहरण बना:

#include <concepts>

class NumberType {};
class BaseBinaryType {};
class NullType {};
class FloatingPointType : public NumberType {};
class IntegerType : public NumberType {};
class BinaryType: public BaseBinaryType {};

template <typename T>
  requires std::is_base_of_v<NumberType, T> || std::is_base_of_v<BaseBinaryType, T>
void PrintValue(T v) {}

int main() {
  PrintValue(FloatingPointType{});
  PrintValue(NullType{});
  return 0;
}

requires में हम || का उपयोग कर सकते हैं, ऊपर के उदाहरण में NullType आया है, वह requires पूरा नहीं करता, इसलिए compile होगा:

concept_requires.cc:16:13: error: no matching function for call to 'PrintValue(NullType)'
   16 |   PrintValue(NullType{});
      |   ~~~~~~~~~~^~~~~~~~~~~~
concept_requires.cc:12:6: note: candidate: 'template<class T>  requires (is_base_of_v<NumberType, T>) || (is_base_of_v<BaseBinaryType, T>) void PrintValue(T)'
   12 | void PrintValue(T v) {}
      |      ^~~~~~~~~~
concept_requires.cc:12:6: note:   template argument deduction/substitution failed:
concept_requires.cc:12:6: note: constraints not satisfied
concept_requires.cc: In substitution of 'template<class T>  requires (is_base_of_v<NumberType, T>) || (is_base_of_v<BaseBinaryType, T>) void PrintValue(T) [with T = NullType]':
concept_requires.cc:16:13:   required from here
concept_requires.cc:12:6:   required by the constraints of 'template<class T>  requires (is_base_of_v<NumberType, T>) || (is_base_of_v<BaseBinaryType, T>) void PrintValue(T)'
concept_requires.cc:11:45: note: no operand of the disjunction is satisfied
   11 |   requires std::is_base_of_v<NumberType, T> || std::is_base_of_v<BaseBinaryType, T>
      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1.3 concept और auto

जब auto आए, हम उसे concept के साथ उपयोग कर सकते हैं, उदाहरण के लिए:

auto add(auto x, auto y) {
  return x + y;
}

हम इसे ऐसे कर सकते हैं:

template <typename T>
concept Addable = requires(T a, T b) { a + b; };

auto add2(Addable auto x, Addable auto y) {
  return x + y;
}

Compile के समय आएगा:

concept_auto.cc:17:20: error: no matching function for call to 'add2(Foo&, Foo&)'
   17 |   std::cout << add2(f1, f2) << std::endl;
      |                ~~~~^~~~~~~~
concept_auto.cc:10:6: note: candidate: 'template<class auto:18, class auto:19>  requires (Addable<auto:18>) && (Addable<auto:19>) auto add2(auto:18, auto:19)'
   10 | auto add2(Addable auto x, Addable auto y) {
      |      ^~~~
concept_auto.cc:10:6: note:   template argument deduction/substitution failed:
concept_auto.cc:10:6: note: constraints not satisfied
concept_auto.cc: In substitution of 'template<class auto:18, class auto:19>  requires (Addable<auto:18>) && (Addable<auto:19>) auto add2(auto:18, auto:19) [with auto:18 = Foo; auto:19 = Foo]':
concept_auto.cc:17:20:   required from here
concept_auto.cc:8:9:   required for the satisfaction of 'Addable<auto:18>' [with auto:18 = Foo]
concept_auto.cc:8:19:   in requirements with 'T a', 'T b' [with T = Foo]
concept_auto.cc:8:42: note: the required expression '(a + b)' is invalid
    8 | concept Addable = requires(T a, T b) { a + b; };

2.कंपाइलर समर्थन

GCC (10.0+), Clang (10.0+) चाहिए, compile विकल्प: -std=c++20/-std=c++2a

https://en.cppreference.com/w/cpp/compiler_support

3.सारांश

C++20 द्वारा दिए गए concept के बाद, हमें अब enable_if/SFINAE तंत्र और function overloading से टेम्प्लेट constraint जाँचने की ज़रूरत नहीं, concept इस काम को संभाल लेता है, यह टेम्प्लेट पैरामीटर constraints का एक स्पष्ट और शक्तिशाली तंत्र देता है, जिससे टेम्प्लेट कोड लिखना, समझना और रखरखाव आसान हो जाता है। Compile समय type checking से, यह कोड की मज़बूती और पठनीयता बढ़ाने में मदद करता है।

संबंधित विषय

टिप्पणियाँ