Thread: SFINAE fun (or failure...)

  1. #1
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654

    SFINAE fun (or failure...)

    Goals:
    - Enable static polymorphism. Call a function if and if it exists.

    Problems:
    - Implementation defined (or undefined behaviour).

    Code:
    Code:
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    #include <functional>
    
    template<typename T>
    struct Has_Subscribe
    {
    	typedef char yes[1];
    	typedef char no[2];
    	template<typename C> static yes& test(decltype(C::Subscribe)*);
    	template<typename C> static no& test(...);
    
    	const static bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    };
    
    template<typename Parent_t>
    struct base
    {
    	base()
    	{
    		test<Parent_t>();
    	}
    
    	template<typename T>
    	auto test() -> typename std::enable_if<Has_Subscribe<T>::value>::type { std::cout << "yes!\n"; }
    	
    	template<typename T>
    	auto test() -> typename std::enable_if<!Has_Subscribe<T>::value>::type { std::cout << "no!\n"; }
    };
    
    struct foo: public base<foo>
    {
    	void Subscribe(int) {}
    };
    
    struct foo2: public base<foo2> {};
    
    int main() 
    {
    	foo _foo;
    	foo2 _foo2;
    	std::cout << "\n";
    }
    VS output:
    yes!
    no!
    (Correct output. In this case.)

    Clang output:
    no!
    no!
    (Incorrect output in this case.)

    This is a sample demonstrating the problem. In the real code, I tend to get even worse results, leading me to think that this is the source of the problem.
    Either there is something I am not aware of, or this method of detecting if members exist do not work.
    Or there are bugs in the compilers.
    Anyone who can shed some light on the problem?
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  2. #2
    Registered User
    Join Date
    Aug 2010
    Location
    Poland
    Posts
    733
    Code:
    struct foo: public base<foo>
    {
        void Subscribe(int) {}
    };
    When base<foo> is instantiated, foo is an incomplete type.

  3. #3
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Bad example, I suppose. Here is another one:
    Code:
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    #include <functional>
    
    template<typename T>
    struct Has_Subscribe
    {
    	typedef char yes[1];
    	typedef char no[2];
    	template<typename C> static yes& test(decltype(C::Subscribe)*);
    	template<typename C> static no& test(...);
    
    	const static bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    };
    
    template<typename Parent_t>
    struct base
    {
    	void bla() { test<Parent_t>(); }
    
    	template<typename T>
    	auto test() -> typename std::enable_if<Has_Subscribe<T>::value>::type { std::cout << "yes!\n"; }
    	
    	template<typename T>
    	auto test() -> typename std::enable_if<!Has_Subscribe<T>::value>::type { std::cout << "no!\n"; }
    };
    
    struct foo: public base<foo>
    {
    	void Subscribe(int) {}
    };
    
    struct foo2: public base<foo2> {};
    
    int main() 
    {
    	foo _foo;
    	foo2 _foo2;
    	_foo.bla();
    	_foo2.bla();
    	std::cout << "\n";
    }
    foo::bla will not be instantiated until the call _foo.bla() in main, by which point foo will be fully declared. Still, same output.
    Unless I am mistaken about when templates are evaluated.
    Maybe it suffers from the same problem as bla is compiled when foo is instantiated?

    EDIT:
    But then again, this doesn't work either:
    Code:
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    #include <functional>
    
    template<typename T>
    struct Has_Subscribe
    {
    	typedef char yes[1];
    	typedef char no[2];
    	template<typename C> static yes& test(decltype(C::Subscribe)*);
    	template<typename C> static no& test(...);
    
    	const static bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    };
    
    template<typename Parent_t>
    struct base
    {
    	template<typename NewParentT>
    	void bla() { test<NewParentT>(); }
    
    	template<typename T>
    	auto test() -> typename std::enable_if<Has_Subscribe<T>::value>::type { std::cout << "yes!\n"; }
    	
    	template<typename T>
    	auto test() -> typename std::enable_if<!Has_Subscribe<T>::value>::type { std::cout << "no!\n"; }
    };
    
    struct foo: public base<foo>
    {
    	void Subscribe(int) {}
    };
    
    struct foo2: public base<foo2> {};
    
    int main() 
    {
    	foo _foo;
    	foo2 _foo2;
    	_foo.bla<foo>();
    	_foo2.bla<foo2>();
    	std::cout << "\n";
    }
    Clearly, there is something clang does not like here.

    To make matters worse, gcc 4.8 (although it doesn't seem to accept -std=c++11, only -std=c++0x, weird) complains that it is an error to use a non-static member function in that way.
    But changing

    template<typename C> static yes& test(decltype(C::Subscribe)*);
    to
    template<typename C> static yes& test(decltype(&C::Subscribe)*);

    makes it compile in all three compilers.
    However, then the output becomes:

    MSVC:
    yes!
    yes!

    Gcc:
    yes!
    no!

    clang:
    yes!
    no!

    Nightmare >_<
    Maybe I should use a hack? What is the portable way of doing this?
    Last edited by Elysia; 05-13-2013 at 07:43 AM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  4. #4
    Registered User
    Join Date
    Aug 2010
    Location
    Poland
    Posts
    733
    You can rely on the function's return type:

    Code:
    template<typename T>
    struct Has_Subscribe
    {
        typedef char yes[1];
        typedef char no[2];
        template<typename C> static yes& test(decltype(static_cast<C*>(nullptr)->Subscribe(0))*);
        template<typename C> static no& test(...);
     
        const static bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    };
    It seems to work on GCC 4.7.3 and MSVC 2010.

    Note: use nullptr as parameter factory. If your member function takes object by reference, dereference nullptr. If it takes object by value, dereference nullptr and pass copy. And so on...
    Last edited by kmdv; 05-13-2013 at 11:50 AM.

  5. #5
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Well, actually, all three compilers seem to accept

    Code:
    template<typename T>
    struct Has_Subscribe
    {
    	typedef char yes[1];
    	typedef char no[2];
    	template<typename C> static yes& test(decltype(&C::Subscribe));
    	template<typename C> static no& test(...);
    
    	const static bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    };
    (i.e. removing the *)

    nullptr, being a pointer, binds stronger to a pointer type than to a function that takes anything.
    Still, the bug in the original code still exists, so the bug hunt goes on...
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  6. #6
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    static_cast<C*>(nullptr)->Subscribe(0)
    O_o

    Bad idea; a compiler is allowed to complain about the cast because it implies chasing a null, and as expected, some will complain.

    Still, the bug in the original code still exists, so the bug hunt goes on...
    You should doctor the type. The more specific flavor the less likely the compiler will travel the wrong direction.

    Soma

    Code:
    #include <iostream>
    
    template<typename T>
    struct Has_Subscribe
    {
        typedef char yes[1];
        typedef char no[2];
        template<typename C, C> struct checker;
        template<typename C> static yes& test(checker<void (C::*)(int), &C::Subscribe> *);
        template<typename C> static no& test(...);
    
        const static bool value = sizeof(test<T>(0)) == sizeof(yes);
    };
    
    template<typename Parent_t>
    struct base
    {
        template<typename NewParentT>
        void bla() { std::cout << Has_Subscribe<Parent_t>::value << '\n'; }
    };
    
    struct foo: public base<foo>
    {
        void Subscribe(int) {}
    };
    
    struct foo2: public base<foo2> {};
    
    int main()
    {
        foo _foo;
        foo2 _foo2;
        _foo.bla<foo>();
        _foo2.bla<foo2>();
        std::cout << "\n";
    }
    Code:
    /* C++11 */
    
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    #include <functional>
     
    template<typename T>
    struct Has_Subscribe
    {
        typedef char yes[1];
        typedef char no[2];
        template<typename C, C> struct checker;
        template<typename C> static yes& test(checker<decltype(C::Subscribe), &C::Subscribe> *);
        template<typename C> static no& test(...);
     
        const static bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    };
     
    template<typename Parent_t>
    struct base
    {
        base()
        {
            test<Parent_t>();
        }
     
        template<typename T>
        auto test() -> typename std::enable_if<Has_Subscribe<T>::value>::type { std::cout << "yes!\n"; }
         
        template<typename T>
        auto test() -> typename std::enable_if<!Has_Subscribe<T>::value>::type { std::cout << "no!\n"; }
    };
     
    struct foo: public base<foo>
    {
        void Subscribe(int) {}
    };
     
    struct foo2: public base<foo2> {};
     
    int main()
    {
        foo _foo;
        foo2 _foo2;
        std::cout << "\n";
    }

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    OK, so found the error. I guess this is because of an "incomplete type". This generates the error:

    Code:
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    #include <functional>
    #include <memory>
    
    template<typename T>
    struct Has_Subscribe
    {
    	typedef char yes[1];
    	typedef char no[2];
    	template<typename C> static yes& test(decltype(&C::Subscribe));
    	template<typename C> static no& test(...);
    
    	const static bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    };
    
    template<bool Enable>
    struct base_base { void print() { std::cout << "yes!\n"; } };
    
    template<>
    struct base_base<false> { void print() { std::cout << "no!\n"; } };
    
    template<typename Parent_t>
    struct base: public base_base<Has_Subscribe<Parent_t>::value>
    {
    	base() { this->print(); }
    };
    
    struct foo: public base<foo>
    {
    	void Subscribe(int) {}
    };
    
    int main() 
    {
    	foo _foo;
    }
    It is not possible to delay the call to print with using a templated member function.

    So the solution I used was to delay instantiation of Has_Subscribe until after the full prototype has been compiled:
    Code:
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    #include <functional>
    #include <memory>
    
    template<typename T>
    struct Has_Subscribe
    {
    	typedef char yes[1];
    	typedef char no[2];
    	template<typename C> static yes& test(decltype(&C::Subscribe));
    	template<typename C> static no& test(...);
    
    	const static bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    };
    
    template<bool Enable>
    struct base_base { void print() { std::cout << "yes!\n"; } };
    
    template<>
    struct base_base<false> { void print() { std::cout << "no!\n"; } };
    
    template<typename Parent_t>
    struct base
    {
    	struct basebase_inst_delayer
    	{
    		base_base<Has_Subscribe<Parent_t>::value> m_basebase;
    	};
    
    	template<typename T>
    	void test() 
    	{
    		m_basebase_ptr = std::unique_ptr<basebase_inst_delayer>(new basebase_inst_delayer());
    		m_basebase_ptr->m_basebase.print(); 
    	}
    
    	std::unique_ptr<basebase_inst_delayer> m_basebase_ptr;
    };
    
    struct foo: public base<foo>
    {
    	void Subscribe(int) {}
    };
    
    struct foo2: public base<foo2> {};
    
    int main() 
    {
    	foo _foo;
    	foo2 _foo2;
    	_foo.test<int>();
    	_foo2.test<int>();
    	std::cout << "\n";
    }
    Now, it would be nice to detect incomplete types to prevent these kinds of errors from happening in the first place... looking into that now.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    Registered User
    Join Date
    Aug 2010
    Location
    Poland
    Posts
    733
    Quote Originally Posted by phantomotap View Post
    Bad idea; a compiler is allowed to complain about the cast because it implies chasing a null, and as expected, some will complain.
    Could you explain? Complain means giving a warning or an error? These expressions are never evaluated, so to my knowledge it is standard conforming and has well-defined behaviour. For some expressions, it would seem to be the only way to test decltype()/noexcept(). static_cast wasn't best cast operator choice through.

    Now, it would be nice to detect incomplete types to prevent these kinds of errors from happening in the first place... looking into that now.
    static_assert(sizeof(T) > 0, "T must be complete."); ?

  9. #9
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by kmdv View Post
    These expressions are never evaluated, so to my knowledge it is standard conforming and has well-defined behaviour. For some expressions, it would seem to be the only way to test decltype()/noexcept(). static_cast wasn't best cast operator choice through.
    Oh, they are evaluated, just not actually executed (or maybe I'm just mixing up the terms, here). Though I am not so sure you may dereference null. Undefined behaviour, you know?
    You can, however, create some function that returns a T. You never have to specify HOW it returns or creates a T if it is never executed.

    static_assert(sizeof(T) > 0, "T must be complete."); ?
    No, not good enough. If T is incomplete at some instance, but defined later on in the same translation unit, then the T must refer to the actual T as defined later on, hence sizeof(T) will return the correct size.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  10. #10
    Registered User
    Join Date
    Aug 2010
    Location
    Poland
    Posts
    733
    Quote Originally Posted by Elysia View Post
    Oh, they are evaluated, just not actually executed (or maybe I'm just mixing up the terms, here). Though I am not so sure you may dereference null. Undefined behaviour, you know?
    You can, however, create some function that returns a T. You never have to specify HOW it returns or creates a T if it is never executed.
    They aren't (7.1.6.2):
    The operand of the decltype specifier is an unevaluated operand (Clause 5).
    Quote Originally Posted by Elysia View Post
    No, not good enough. If T is incomplete at some instance, but defined later on in the same translation unit, then the T must refer to the actual T as defined later on, hence sizeof(T) will return the correct size.
    I don't quite get it, but nvm...

  11. #11
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by kmdv View Post
    They aren't (7.1.6.2):
    Ok, then it's just the term evaluated used in the standard that I do not quite understand.

    I don't quite get it, but nvm...
    It means that if the compiler finds some sizeof(T) and T is an incomplete type, than the compiler must defer evaluating that until T is fully defined (if it is defined in the current translatio unit).
    That's why you will always get the correct size of T regardless of where it's placed.
    I just wish it would apply to SFINAE, as well. But then again, it really is a hack in the standard that is widely abused. Can't say I'm surprised it has side effects...
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  12. #12
    Registered User
    Join Date
    Aug 2010
    Location
    Poland
    Posts
    733
    Quote Originally Posted by Elysia View Post
    It means that if the compiler finds some sizeof(T) and T is an incomplete type, than the compiler must defer evaluating that until T is fully defined (if it is defined in the current translatio unit).
    That's why you will always get the correct size of T regardless of where it's placed.
    I just wish it would apply to SFINAE, as well. But then again, it really is a hack in the standard that is widely abused. Can't say I'm surprised it has side effects...
    How does it defer?

    If you mean that:

    Code:
    class Foo;
    
    template <typename T>
    class Bar
    {
        static_assert(sizeof(T) > 0, "");
    };
    
    class Foo : Bar<Foo>
    {
    };
    // Foo complete at this point.
    It works fine (read: doesn't compile).

  13. #13
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Actually, what I read on the intraweb seems to contradict your and my result. sizeof an incomplete type is a compile error.

    But what I meant was something like this:
    In your example, at line 9, Bar is instantiated. By this time, Foo is incomplete.
    At line 6, you evaluated T == Foo. This will produce a compile error, but that's irrelevant for our example. But note that this is Foo, yet sizeof(Foo) here is undefined (compile error).
    But after line 12, sizeof(Foo) is not undefined, so that implies that this must be a different Foo. But the standard says this cannot be. All Foos (or any type T) must be the same type.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  14. #14
    Registered User
    Join Date
    Aug 2010
    Location
    Poland
    Posts
    733
    Quote Originally Posted by Elysia View Post
    Actually, what I read on the intraweb seems to contradict your and my result. sizeof an incomplete type is a compile error.

    But what I meant was something like this:
    In your example, at line 9, Bar is instantiated. By this time, Foo is incomplete.
    At line 6, you evaluated T == Foo. This will produce a compile error, but that's irrelevant for our example. But note that this is Foo, yet sizeof(Foo) here is undefined (compile error).
    But after line 12, sizeof(Foo) is not undefined, so that implies that this must be a different Foo. But the standard says this cannot be. All Foos (or any type T) must be the same type.
    Can you give a relevant part in the standard, or at least some track?

    Why does it imply that it is a different Foo? It is the same Foo but defined, unless I'm missing something.
    sizeof(x) recieves x which is an incomplete type, and thus raises error. If I understand the way it could defer anything - it cannot defer sizeof() evaluation, because its result may be necessary for further template processing.

  15. #15
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I got it from here (see last reply):
    c++ - How to write `is_complete` template? - Stack Overflow

    It implies it's a different Foo because the compiler has not seen the entire Foo yet.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. SATA HDD failure
    By PING in forum Tech Board
    Replies: 4
    Last Post: 12-23-2008, 12:25 AM
  2. Assertion Failure
    By osal in forum C Programming
    Replies: 11
    Last Post: 06-03-2004, 10:25 PM
  3. A miserable failure
    By Lurker in forum A Brief History of Cprogramming.com
    Replies: 33
    Last Post: 12-21-2003, 08:37 PM
  4. cin failure
    By trekker in forum C++ Programming
    Replies: 3
    Last Post: 06-20-2003, 06:05 PM
  5. Am i a Failure?
    By Failure!!! in forum A Brief History of Cprogramming.com
    Replies: 16
    Last Post: 05-28-2003, 11:50 AM