15

के साथ एक कॉल करने योग्य है या नहीं, मूल रूप से, जो मैं प्राप्त करना चाहता हूं वह संकलित-समय सत्यापन (संभवतः अच्छा त्रुटि संदेश के साथ) है जो कॉल करने योग्य पंजीकृत है (या तो एक फ़ंक्शन, लैम्ब्डा, कॉल के साथ एक स्ट्रक्चर ऑपरेटर) सही हस्ताक्षर है। उदाहरण (static_assert की सामग्री को भरे जाने के लिए कर रहे हैं):टेम्पलेट तर्क किसी दिए गए हस्ताक्षर

struct A { 
    using Signature = void(int, double); 

    template <typename Callable> 
    void Register(Callable &&callable) { 
    static_assert(/* ... */); 
    callback = callable; 
    } 

    std::function<Signature> callback; 
}; 
+2

क्या आप एक समान हस्ताक्षर, या संगत हस्ताक्षर चाहते हैं? उदाहरण के लिए, यदि आपका हस्ताक्षर 'बूल() 'है, तो क्या आप' int (*)()', यानी फ़ंक्शन पॉइंटर को कोई तर्क लेने और पूर्णांक लौटने के लिए स्वीकार करेंगे? पूर्णांक बूल को निहित रूप से परिवर्तित करेगा ताकि यह संगत हो, लेकिन यह समान नहीं है। –

+0

यह एक अच्छा सवाल है - मैं जितना संभव हो उतना मजबूत टाइपिंग करना पसंद करूंगा, इसलिए मेरे उपयोग के मामले में इस मामले में int (*)() को अस्वीकार करना बेहतर होगा। – pzelasko

उत्तर

6

जवाब में से अधिकांश मूल रूप से सवाल का जवाब देने पर ध्यान केंद्रित कर रहे हैं: यह कर सकते हैं आप कॉल इन प्रकार के मूल्यों के साथ दिए गए समारोह वस्तु । यह हस्ताक्षर से मेल खाने जैसा नहीं है, क्योंकि यह कई अंतर्निहित रूपांतरणों की अनुमति देता है जो आप कहते हैं कि आप नहीं चाहते हैं। अधिक सख्त मैच पाने के लिए हमें टीएमपी का एक गुच्छा करना है। सबसे पहले, यह उत्तर: Call function with part of variadic arguments दिखाता है कि तर्क के सटीक प्रकार और कॉल करने योग्य प्रकार के प्रकार कैसे प्राप्त करें। कोड यहाँ reproduced:

template <typename T> 
struct function_traits : public function_traits<decltype(&T::operator())> 
{}; 

template <typename ClassType, typename ReturnType, typename... Args> 
struct function_traits<ReturnType(ClassType::*)(Args...) const> 
{ 
    using result_type = ReturnType; 
    using arg_tuple = std::tuple<Args...>; 
    static constexpr auto arity = sizeof...(Args); 
}; 

template <typename R, typename ... Args> 
struct function_traits<R(&)(Args...)> 
{ 
    using result_type = R; 
    using arg_tuple = std::tuple<Args...>; 
    static constexpr auto arity = sizeof...(Args); 
}; 

कि अब आप अपने कोड में पाता है स्थिर की एक श्रृंखला डाल सकता है किया है,:

struct A { 
    using Signature = void(int, double); 

    template <typename Callable> 
    void Register(Callable &&callable) { 
    using ft = function_traits<Callable>; 
    static_assert(std::is_same<int, 
     std::decay_t<std::tuple_element_t<0, typename ft::arg_tuple>>>::value, ""); 
    static_assert(std::is_same<double, 
     std::decay_t<std::tuple_element_t<1, typename ft::arg_tuple>>>::value, ""); 
    static_assert(std::is_same<void, 
     std::decay_t<typename ft::result_type>>::value, ""); 

    callback = callable; 
    } 

    std::function<Signature> callback; 
}; 

जब से तुम मूल्य से गुजर रहे हैं यह सब आप की जरूरत मूल रूप से है। यदि आप संदर्भ से गुजर रहे हैं, तो मैं एक अतिरिक्त स्थिर जोर जोड़ूंगा जहां आप अन्य उत्तरों में से एक का उपयोग करते हैं; शायद songyuanyao का जवाब। यह उन मामलों का ख्याल रखेगा जहां उदाहरण के लिए आधार प्रकार समान था, लेकिन कॉन्स योग्यता गलत दिशा में गई थी।

आप जो कुछ भी करते हैं (बस स्थैतिक जोर में प्रकारों को दोहराते हुए) के बजाय आप Signature प्रकार के सभी सामान्य बना सकते हैं। यह अच्छा होगा लेकिन इससे पहले से ही गैर-मामूली उत्तर में और भी जटिल टीएमपी जोड़ा होगा; यदि आपको लगता है कि आप इसका उपयोग कई अलग-अलग Signature एस के साथ करेंगे या यह अक्सर बदल रहा है तो शायद उस कोड को जोड़ने के लायक भी है।

यहां एक लाइव उदाहरण है: http://coliru.stacked-crooked.com/a/cee084dce9e8dc09। विशेष रूप से, मेरा उदाहरण:

void foo(int, double) {} 
void foo2(double, double) {} 

int main() 
{ 
    A a; 
    // compiles 
    a.Register([] (int, double) {}); 
    // doesn't 
    //a.Register([] (int, double) { return true; }); 
    // works 
    a.Register(foo); 
    // doesn't 
    //a.Register(foo2); 
} 
8

आप std::is_convertible उपयोग कर सकते हैं (क्योंकि सी ++ 11), उदा

static_assert(std::is_convertible_v<Callable&&, std::function<Signature>>, "Wrong Signature!"); 

या

static_assert(std::is_convertible_v<decltype(callable), decltype(callback)>, "Wrong Signature!"); 

LIVE

+1

यह एक अच्छी तकनीक है, लेकिन यह स्पष्ट नहीं है कि ओपी क्या चाहता है, 'std :: function' मुझे लगता है कि थोड़ा अधिक अनुमोदित है, केवल एक "संगत" हस्ताक्षर की आवश्यकता है और एक समान हस्ताक्षर नहीं है। –

+1

मुझे सादगी के लिए यह वास्तव में पसंद है, हालांकि मैं कठोर तर्क जांच के कारण निर फ्राइडमैन के उत्तर का चयन करने जा रहा हूं। – pzelasko

2

आप एक variadic टेम्पलेट कक्षा में A को बदलने के लिए स्वीकार करते हैं, आप के रूप में

इस प्रकार है, decltype() उपयोग कर सकते हैं, activare को Register केवल तभी callable संगत है
template <typename R, typename ... Args> 
struct A 
{ 
    using Signature = R(Args...); 

    template <typename Callable> 
    auto Register (Callable && callable) 
     -> decltype(callable(std::declval<Args>()...), void()) 
    { callback = callable; } 

    std::function<Signature> callback; 
}; 

इस तरह, आप पसंद करते हैं, एक असंगत समारोह के साथ Register() बुला हैं, तो आप एक नरम त्रुटि प्राप्त और एक अन्य Register() समारोह

void Register (...) 
{ /* do something else */ }; 
1

को सक्रिय आप का पता लगाने मुहावरा है, जो sfinae का एक रूप है उपयोग कर सकते हैं कर सकते हैं। मेरा मानना ​​है कि यह सी ++ 11 में काम करता है।

template <typename...> 
using void_t = void; 

template <typename Callable, typename enable=void> 
struct callable_the_way_i_want : std::false_type {}; 

template <typename Callable> 
struct callable_the_way_i_want <Callable, void_t <decltype (std::declval <Callable>()(int {},double {}))>> : std::true_type {}; 

तो फिर तुम तो जैसे अपने कोड में एक स्थिर ज़ोर लिख सकते हैं:

static_assert (is_callable_the_way_i_want <Callable>::value, "Not callable with required signature!"); 
जवाब से अधिक

इस का लाभ मैं ऊपर देखें है:

  • यह किसी भी प्रतिदेय लिए काम करता है , न सिर्फ एक लैम्ब्डा
  • कोई रनटाइम ओवरहेड या std::function व्यवसाय नहीं है। उदाहरण के लिए std::function गतिशील आवंटन का कारण बन सकता है, जो अन्यथा अनावश्यक होगा।
  • आप वास्तव में परीक्षण के खिलाफ एक static_assert लिखने और टैटन लामा इस तकनीक के बारे में बहुत ऐसे ब्लॉग पोस्ट, और कई विकल्प लिखा एक अच्छा मानव पठनीय त्रुटि संदेश वहाँ

शब्दों में कहें, यह बाहर की जाँच कर सकते हैं! https://blog.tartanllama.xyz/detection-idiom/

यदि आपको यह बहुत कुछ करने की आवश्यकता है तो आप callable_traits लाइब्रेरी को देखना चाहेंगे।

4

सी ++ 17 में is_invocable<Callable, Args...> विशेषता है, जो वास्तव में आप पूछते हैं। is_convertible<std::function<Signature>,...> पर इसका लाभ यह है कि आपको रिटर्न प्रकार निर्दिष्ट करने की आवश्यकता नहीं है। यह ओवरकिल की तरह लग सकता है लेकिन हाल ही में मुझे समस्या का सामना करना पड़ा था, वास्तव में मेरे रैपर फ़ंक्शन ने पारित कॉलेबल से अपना रिटर्न टाइप घटाया था, लेकिन मैंने टेम्पलेटेड लैम्ब्डा को इस [](auto& x){return 2*x;} की तरह पास कर दिया है, इसलिए इसे वापस करने के लिए सबकॉल में कटौती की गई थी । मैं इसे std::function में परिवर्तित नहीं कर सका और मैं सी ++ 14 के लिए is_invocable के स्थानीय कार्यान्वयन का उपयोग कर समाप्त हुआ। मुझे वह लिंक नहीं मिल रहा है जहां से मुझे यह मिला ...वैसे भी, कोड:

template <class F, class... Args> 
struct is_invocable 
{ 
    template <class U> 
    static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type()); 
    template <class U> 
    static auto test(...) -> decltype(std::false_type()); 

    static constexpr bool value = decltype(test<F>(0))::value; 
}; 

और अपने उदाहरण के लिए:

struct A { 
using Signature = void(int, double); 

template <typename Callable> 
void Register(Callable &&callable) { 
    static_assert(is_invocable<Callable,int,double>::value, "not foo(int,double)"); 
    callback = callable; 
} 

std::function<Signature> callback; 
}; 
संबंधित मुद्दे