সি ++ SFINAE উদাহরণ?


122

আমি আরও টেম্পলেট মেটা-প্রোগ্রামিংয়ে যেতে চাই। আমি জানি যে SFINAE এর অর্থ দাঁড়ায় "প্রতিস্থাপন ব্যর্থতা কোনও ত্রুটি নয়" " তবে কেউ কি আমাকে SFINAE এর জন্য ভাল ব্যবহার দেখাতে পারে?


2
এটা একটা ভালো প্রশ্ন। আমি SFINAE কে বেশ ভালভাবে বুঝতে পেরেছি, তবে আমার মনে হয় না যে এটি কখনও ব্যবহার করতে হবে (যদি না গ্রন্থাগারগুলি এটি না জেনে আমি এটি না করে)।
জিফ্রে

5
এসটিএল এটিকে এফএকিউগুলিতে কিছুটা আলাদাভাবে এখানে রেখেছিল, "সাবস্টিটিউশন ব্যর্থতা একটি
হাতী

উত্তর:


72

এখানে একটি উদাহরণ ( এখানে থেকে ):

template<typename T>
class IsClassT {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    // Will be chosen if T is anything except a class.
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
    enum { No = !Yes };
};

যখন IsClassT<int>::Yesমূল্যায়ন করা হয়, 0 তে রূপান্তর করা int int::*যায় না কারণ কোন শ্রেণি নয়, সুতরাং এর কোনও সদস্য পয়েন্টার থাকতে পারে না। যদি SFINAE না থাকে, তবে আপনি একটি সংকলক ত্রুটি পেয়ে যাবেন, '0 এর মতো কিছু অ-শ্রেণীর টাইপ ইন্টের জন্য সদস্য পয়েন্টারে রূপান্তরিত হতে পারে না'। পরিবর্তে, এটি কেবল ...ফর্মটি ব্যবহার করে যা দুটি ফেরত দেয়, এবং সুতরাং এটি মিথ্যাতে মূল্যায়ন করে, কোন শ্রেণীর ধরণের নয়।


8
@ আরলবন্ড, আমি আপনার প্রশ্নের উত্তরটি এখানে এই প্রশ্নের মন্তব্যে দিয়েছি: stackoverflow.com/questions/822059/… । সংক্ষেপে: যদি উভয় পরীক্ষার ফাংশন প্রার্থী এবং কার্যকর হয়, তবে "..." এর নিকৃষ্টতম রূপান্তর ব্যয় হয় এবং তাই অন্য ফাংশনের পক্ষে কখনও নেওয়া হবে না। "..." হ'ল উপবৃত্ত, ভের-আরগ জিনিস: ইন প্রিন্টফ (চর কনস্ট *, ...);
জোহানেস স্কাউব - লিটব


20
এখানে অদ্ভুত জিনিসটি আইএমও নয় ..., বরং এটি ছিল int C::*যা আমি কখনও দেখিনি এবং সন্ধান করতে হবে। জন্য উত্তর পাওয়া কি যে আর কোনটা তা এখানে জন্য ব্যবহার করা যেতে পারে: stackoverflow.com/questions/670734/...
HostileFork বলেছেন Dont ট্রাস্ট দঃপূঃ

1
সি :: * কী কী কেউ ব্যাখ্যা করতে পারে? আমি সমস্ত মন্তব্য এবং লিঙ্কগুলি পড়েছি, তবে আমি এখনও অবাক হয়েছি, সি সি :: * এর অর্থ এটি ইন্ট টাইপের সদস্য পয়েন্টার। কোন শ্রেণীর যদি কোন প্রকারের সদস্য না থাকে তবে কী হবে? আমি কী মিস করছি? এবং পরীক্ষায় কীভাবে <T> (0) এটি খেলতে পারে? আমি অবশ্যই কিছু মিস করছি
ব্যবহারকারী 2584960

92

আমি SFINAEবুলিয়ান শর্ত পরীক্ষা করতে ব্যবহার করতে চাই ।

template<int I> void div(char(*)[I % 2 == 0] = 0) {
    /* this is taken when I is even */
}

template<int I> void div(char(*)[I % 2 == 1] = 0) {
    /* this is taken when I is odd */
}

এটি বেশ কার্যকর হতে পারে। উদাহরণস্বরূপ, আমি অপারেটর কমা ব্যবহার করে সংগ্রহ করা সূচনা তালিকা স্থির আকারের চেয়ে বেশি কিনা তা পরীক্ষা করতে এটি ব্যবহার করেছি

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

তালিকাটি কেবল তখনই গ্রহণ করা হয় যখন এম এন এর চেয়ে ছোট হয়, যার অর্থ যে আরম্ভকারী তালিকায় খুব বেশি উপাদান নেই।

বাক্য গঠনটির char(*)[C]অর্থ: উপাদান টাইপের চর এবং আকারের সাথে একটি অ্যারেতে পয়েন্টার C। যদি Cমিথ্যা হয় (0 এখানে), তবে আমরা অবৈধ প্রকারটি পাই char(*)[0], একটি শূন্য আকারের অ্যারেটির পয়েন্টার: SFINAE এটি তৈরি করে যাতে টেমপ্লেটটিকে এড়ানো হবে।

এর সাথে প্রকাশিত boost::enable_if, এটি দেখতে এটির মতোই

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, 
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}

অনুশীলনে, আমি প্রায়শই শর্তাদি একটি দরকারী ক্ষমতা যাচাই করার ক্ষমতা খুঁজে পাই।


1
@ জোহানেস অদ্ভুতভাবে যথেষ্ট, জিসিসি (4.8) এবং ক্ল্যাং (3.2) 0 টি আকারের অ্যারেগুলি ঘোষণা করতে স্বীকার করে (যাতে প্রকারটি আসলে "অবৈধ" নয়) তবে এটি আপনার কোডের সাথে সঠিকভাবে আচরণ করে। SFINAE বনাম "নিয়মিত" ধরণের ব্যবহারের ক্ষেত্রে এই ক্ষেত্রে সম্ভবত বিশেষ সমর্থন রয়েছে।
আকিম

@ কিম: যদি তা কখনও সত্য হয় (অদ্ভুত?! কখন থেকে?) তবে M <= N ? 1 : -1তার পরিবর্তে সম্ভবত কাজ করতে পারে।
v.oddou

1
@ v.oddou শুধু চেষ্টা করুন int foo[0]। এটির সমর্থিত আমি অবাক হই না, কারণ এটি খুব কার্যকর " কাঠামোটি 0-দৈর্ঘ্যের অ্যারে দিয়ে শেষ হওয়া" কৌশল ( gcc.gnu.org/onlinesocs/gcc/Zero-Length.html ) এর অনুমতি দেয়।
আকিম

@ কিম: হ্যাঁ আমি যা ভেবেছিলাম তা -> সি 99। এটি সি ++ তে অনুমোদিত নয়, আপনি একটি আধুনিক error C2466: cannot allocate an array of constant size 0
সংকলকটি

1
@ v.oddou না, আমি আসলে C ++ এবং আসলে C ++ 11 বোঝাতে চাইছিলাম: ঝনঝন ++ এবং g ++ উভয়ই এটি গ্রহণ করে এবং আমি কেন এমন একটি পৃষ্ঠাতে ইঙ্গিত করেছি যা এটি দরকারী explains
আকিম

16

সি ++ এ 11 SFINAE পরীক্ষাগুলি অনেক সুন্দর হয়ে উঠেছে। এখানে সাধারণ ব্যবহারের কয়েকটি উদাহরণ দেওয়া হল:

বৈশিষ্ট্যের উপর নির্ভর করে একটি ফাংশন ওভারলোড চয়ন করুন

template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
    //integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
    //floating point version
}

তথাকথিত টাইপ সিঙ্ক আইডিয়োম ব্যবহার করে আপনি কোনও ধরণের সদস্যের কিনা তা পরীক্ষা করার মতো এবং যদি সেই সদস্যটি কোনও নির্দিষ্ট ধরণের হয় তবে যাচাই করার মতো এক ধরণের সুন্দর স্বেচ্ছাচারিত পরীক্ষা করতে পারেন

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
    std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};


struct S{
   int bar;
};
struct K{

};

template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
    std::cout << "has bar" << std::endl;
}
void print(...){
    std::cout << "no bar" << std::endl;
}

int main(){
    print(S{});
    print(K{});
    std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}

এখানে একটি সরাসরি উদাহরণ: http://ideone.com/dHhyHE আমি সম্প্রতি আমার ব্লগে SFINAE এবং ট্যাগ প্রেরণের উপর একটি সম্পূর্ণ বিভাগও লিখেছি (নির্লজ্জ প্লাগ তবে প্রাসঙ্গিক) http://metaporky.blogspot.de/2014/08/ পার্ট 7-স্ট্যাটিক-প্রেরণ-function.html

সি ++ ১৪ হিসাবে নোট করুন এখানে একটি std :: void_t রয়েছে যা মূলত আমার টাইপসিংকের মতোই এখানে।


আপনার কোডের প্রথম ব্লক একই টেম্পলেটটিকে নতুন সংজ্ঞা দেয়।
টিসি

যেহেতু_ইনটিগ্রাল এবং ইস_ফ্লোটিং_পয়েন্ট উভয়ই সঠিক তাই এটি উভয়ই সত্য হওয়া উচিত বা কারণ এসএফআইএনএ কমপক্ষে একটি সরিয়ে ফেলবে।
odinthenerd

আপনি বিভিন্ন ডিফল্ট টেম্পলেট আর্গুমেন্টের সাথে একই টেম্পলেটটিকে পুনরায় সংজ্ঞা দিচ্ছেন। আপনি কি এটি সংকলনের চেষ্টা করেছেন?
টিসি

2
আমি রূপান্তরিত টেম্পলেটটিতে নতুন তাই আমি এই উদাহরণটি বুঝতে চেয়েছিলাম। আপনি কোনও TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>জায়গায় এবং তার TypeSinkT<decltype(&T::bar)>পরে অন্য জায়গায় ব্যবহার করার কোনও কারণ আছে ? এছাড়াও &প্রয়োজনীয় std::declval<T&>?
কেভিন ডায়ন

1
আপনার সম্পর্কে TypeSink, সি ++ 17 আছে std::void_t:)
ওয়াইএসসি

10

বুস্টের সক্ষম_আইএফ লাইব্রেরিতে এসএফআইএনএ ব্যবহারের জন্য একটি দুর্দান্ত ক্লিন ইন্টারফেস সরবরাহ করা হয়েছে। আমার প্রিয় ব্যবহারের উদাহরণগুলির মধ্যে একটি হ'ল বুস্ট.আইট্রেটর লাইব্রেরিতে। SFINAE পুনরাবৃত্তকারী ধরণের রূপান্তর সক্ষম করতে ব্যবহৃত হয়।


4

সি ++ 17 সম্ভবত বৈশিষ্ট্যগুলির অনুসন্ধানের জন্য একটি সাধারণ উপায় সরবরাহ করবে। বিশদগুলির জন্য N4502 দেখুন , তবে স্ব-অন্তর্ভুক্ত উদাহরণ হিসাবে নিম্নলিখিতটি বিবেচনা করুন।

এই অংশটি ধ্রুবক অংশ, এটি একটি শিরোনামে রাখুন।

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

নিম্নলিখিত উদাহরণটি, N4502 থেকে নেওয়া , ব্যবহার দেখায়:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

অন্যান্য বাস্তবায়নের তুলনায়, এটি মোটামুটি সহজ: সরঞ্জামের একটি হ্রাসিত সেট ( void_tএবং detect) যথেষ্ট। তদতিরিক্ত , এটি জানানো হয়েছিল ( এন 4502 দেখুন ) এটি পূর্ববর্তী পদ্ধতির চেয়ে পরিমাপযোগ্যভাবে আরও দক্ষ (সংকলন-সময় এবং সংকলক মেমরির খরচ) is

এখানে একটি লাইভ উদাহরণ দেওয়া আছে , এতে জিসিসির প্রাক 5.1 এর জন্য বহনযোগ্যতার টুইটগুলি অন্তর্ভুক্ত রয়েছে।


3

এখানে আরেকটি (প্রয়াত) এর SFINAE উদাহরণস্বরূপ, উপর ভিত্তি করে গ্রেগ রজার্স এর উত্তর :

template<typename T>
class IsClassT {
    template<typename C> static bool test(int C::*) {return true;}
    template<typename C> static bool test(...) {return false;}
public:
    static bool value;
};

template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);

এইভাবে, আপনি শ্রেণীর valueকিনা তা দেখতে মানটির মূল্য পরীক্ষা করতে পারেন T:

int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}

int C::*আপনার উত্তরের এই বাক্য গঠনটির অর্থ কী? C::*প্যারামিটারের নাম কীভাবে হতে পারে?
কিরিল কোবেলেভ

1
এটা সদস্য একটি পয়েন্টার। কিছু রেফারেন্স: isocpp.org/wiki/faq/pointers-to- মেম্বার্স
whoan

@ কিরিলকোব্লেভ int C::*হ'ল একটি intসদস্য ভেরিয়েবলের পয়েন্টারের প্রকার C
YSC

3

এখানে SFINAE এর একটি ভাল নিবন্ধ: সি ++ এর SFINAE ধারণার একটি পরিচিতি: একটি শ্রেণীর সদস্যের সংকলন-সময়ের আত্মপরিচয়

এটি নিম্নলিখিত হিসাবে সংক্ষিপ্তসার:

/*
 The compiler will try this overload since it's less generic than the variadic.
 T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
 int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
 It simply tries the next overload. 
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }

// The sink-hole.
void f(...) { }

f(1); // Calls void f(...) { }

template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.

template<class T> // A specialisation used if the expression is true. 
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.

template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return obj.serialize();
}

template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return to_string(obj);
}

declvalএমন একটি ইউটিলিটি যা আপনাকে এমন কোনও ধরণের অবজেক্টকে "জাল রেফারেন্স" দেয় যা সহজেই তৈরি করা যায় না। declvalআমাদের SFINAE নির্মাণের জন্য সত্যই কার্যকর।

struct Default {
    int foo() const {return 1;}
};

struct NonDefault {
    NonDefault(const NonDefault&) {}
    int foo() const {return 1;}
};

int main()
{
    decltype(Default().foo()) n1 = 1; // int n1
//  decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
    std::cout << "n2 = " << n2 << '\n';
}

0

এখানে, আমি পয়েন্টারটি কোনও ফাংশন বা সদস্য শ্রেণি পয়েন্টার কিনা তা নির্ধারণের জন্য টেম্পলেট ফাংশন ওভারলোডিং (সরাসরি SFINAE নয়) ব্যবহার করছি: ( iostream cout / cerr সদস্য ফাংশন পয়েন্টার 1 বা সত্য হিসাবে মুদ্রিত করা সম্ভব? )

https://godbolt.org/z/c2NmzR

#include<iostream>

template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
    return true;
}

template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
    return true;
}

template<typename... Args>
constexpr bool is_function_pointer(Args...) {
    return false;
}

struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main(void) {
    int* var;

    std::cout << std::boolalpha;
    std::cout << "0. " << is_function_pointer(var) << std::endl;
    std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
    std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
    std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
    std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
    return 0;
}

ছাপে

0. false
1. true
2. true
3. true
4. true

কোড, এটা পারে (কম্পাইলার "ভালো" উপর নির্ভর করে হবে) একটি ফাংশন যা সত্য বা মিথ্যা ফিরে আসবে করার জন্য একটি রান টাইম কল উৎপন্ন। আপনি যদি is_function_pointer(var)কম্পাইল টাইপ (রান সময় কোনও ফাংশন কল করা হয় না) মূল্যায়ন করতে বাধ্য করতে চান , আপনি constexprভেরিয়েবল ট্রিক ব্যবহার করতে পারেন :

constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;

সি ++ স্ট্যান্ডার্ড অনুসারে সমস্ত চলকগুলিconstexpr সংকলনের সময় মূল্যায়নের গ্যারান্টিযুক্ত (সংকলনের সময় একটি সি স্ট্রিংয়ের গণনার দৈর্ঘ্য this এটি কি আসলেই একটি কনস্টেক্সারপ? )।


0

নিম্নলিখিত ধরণের কোডটি SFINAE ব্যবহার করে সংকলককে কোনও প্রকারের নির্দিষ্ট পদ্ধতি রয়েছে কি না তার উপর ভিত্তি করে একটি ওভারলোড নির্বাচন করতে দেয়:

    #include <iostream>
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_int()) = 0) {
        std::cout << "Int: " <<  value.get_int() << std::endl;
    }
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_float()) = 0) {
        std::cout << "Float: " << value.get_float() << std::endl;
    }
    
    
    struct FloatItem {
        float get_float() const {
            return 1.0f;
        }
    };
    
    struct IntItem {
        int get_int() const {
            return -1;
        }
    };
    
    struct UniversalItem : public IntItem, public FloatItem {};
    
    int main() {
        do_something(FloatItem{});
        do_something(IntItem{});
        // the following fails because template substitution
        // leads to ambiguity 
        // do_something(UniversalItem{});
        return 0;
    }

আউটপুট:

ভাসা: 1
ইন্ট: -১
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.