अनिर्दिष्ट प्रकारों का उपयोग C ++ 20 में कैसे किया जा सकता है?
मैं आवश्यकता व्यक्त करने के लिए C ++ 20 अवधारणा लिखने की कोशिश कर रहा हूं कि एक प्रकार की एक निश्चित विधि होती है, जो एक तर्क लेती है, लेकिन इस अवधारणा के प्रयोजनों के लिए मुझे परवाह नहीं है कि तर्क प्रकार क्या है।
मैंने कुछ लिखने की कोशिश की है:
template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
हालाँकि, दोनों gcc और clang इसे अस्वीकार करते हैं, एक त्रुटि देते हुए कि 'auto' का उपयोग इस तरह की आवश्यकता अभिव्यक्ति के पैरामीटर सूची में नहीं किया जा सकता है।
एक विकल्प दूसरे टेम्पलेट पैरामीटर के रूप में 'x' के प्रकार को रखना होगा:
template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
लेकिन इसके बाद TX को स्पष्ट रूप से निर्दिष्ट करने की आवश्यकता होती है जब भी अवधारणा का उपयोग किया जाता है, तो इसे घटाया नहीं जा सकता है:
struct S { void Foo(int); };
static_assert(HasFooMethod<S>); // doesn't compile
static_assert(HasFooMethod<S, int>); // the 'int' must be specified
क्या कोई अवधारणा लिखने का कोई तरीका है जो फू को अनिर्दिष्ट प्रकार का तर्क देने की अनुमति देता है ?
प्रश्न संकल्पित परिभाषा के लिए विवश टेम्पलेट सदस्य सदस्य फ़ंक्शन की आवश्यकता बहुत समान है, लेकिन समान नहीं है: यह प्रश्न पूछता है कि कैसे आवश्यकता होती है कि एक (टेम्प्लेटेड) विधि किसी दिए गए अवधारणा को संतुष्ट करने के लिए किसी भी प्रकार को ले सकती है , जबकि यह प्रश्न उस विधि की आवश्यकता के बारे में है कुछ विशेष प्रकार, हालांकि वह प्रकार अनिर्दिष्ट है। क्वांटिफ़ायर के संदर्भ में, दूसरा प्रश्न सार्वभौमिक परिमाणीकरण के बारे में (बाध्य) पूछ रहा है जबकि यह अस्तित्वगत परिमाणीकरण के बारे में है। दूसरे प्रश्न का उत्तर मेरे मामले पर भी लागू नहीं होता है।
जवाब
अवधारणाओं का उद्देश्य उस प्रकार की कार्यक्षमता प्रदान करना नहीं है जिसे आप खोज रहे हैं। इसलिए वे इसे प्रदान नहीं करते हैं।
एक अवधारणा का मतलब टेम्पलेट्स को बाध्य करना है, ऐसे भावों या बयानों का एक सेट निर्दिष्ट करने के लिए जो टेम्पलेट अपनी परिभाषा में उपयोग (या कम से कम उपयोग करने के लिए स्वतंत्र) का इरादा रखता है।
टेम्पलेट के भीतर जो आप बहुत विवश हैं, यदि आप अभिव्यक्ति लिखते हैं t.Foo(x)
, तो आप जानते हैं कि किस प्रकार का है x
। यह या तो एक ठोस प्रकार, एक टेम्पलेट पैरामीटर, या एक टेम्पलेट पैरामीटर से प्राप्त नाम है। किसी भी तरह से, x
विवश होने वाले टेम्पलेट पर उपलब्ध प्रकार है।
इसलिए यदि आप इस तरह के टेम्पलेट को कसना चाहते हैं, तो आप दोनों के प्रकार t
और प्रकार का उपयोग करते हैं x
। उस समय दोनों आपके लिए उपलब्ध हैं, इसलिए इस तरह की बाधा पैदा करने में कोई समस्या नहीं है। यही है, बाधा T
एक अलग प्रकार के रूप में नहीं है ; इसके बीच संबंध पर है T
और X
।
कॉन्सेप्ट के उपयोग की वास्तविक जगह के साथ किसी भी संबंध से रहित होने के लिए अवधारणाओं का अर्थ एक वैक्यूम में काम करना नहीं है। आपको एकात्मक अवधारणाएँ बनाने पर ध्यान केंद्रित नहीं करना चाहिए ताकि उपयोगकर्ता static_assert
उनके विरुद्ध अपनी कक्षाएं चला सकें। यदि परीक्षण एक प्रकार से उन्हें पूरा करता है (जो मूल रूप से आपके static_assert
काम कर रहा है) के लिए अवधारणाओं का मतलब नहीं है ; वे टेम्पलेट परिभाषा का उपयोग करने के लिए बाध्य हैं जो उनका उपयोग करता है।
आपके लिए अड़चन की जरूरत है FooCallableWith
, नहीं HasFooMethod
।
इसके करीब कुछ एक एडाप्टर प्रकार को परिभाषित करके पूरा किया जा सकता है जो कुछ भी (लगभग) को रूपांतरित कर सकता है:
struct anything
{
// having both these conversions allows Foo's argument to be either
// a value, an lvalue reference, or an rvalue reference
template <typename T>
operator T&();
template <typename T>
operator T&&();
};
ध्यान दें कि इन ऑपरेटरों को लागू करने की आवश्यकता नहीं है, क्योंकि उनका उपयोग केवल एक अज्ञात संदर्भ में किया जाएगा (और वास्तव में, वे सभी प्रकार टी के लिए लागू नहीं किए जा सकते हैं)।
फिर, के HasFooMethod
रूप में लिखा जा सकता है:
template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
{ t.Foo(a) } -> std::same_as<void>;
};