“What happened to my SFINAE” redux: conditional template class members?

后端 未结 2 1197
粉色の甜心
粉色の甜心 2020-12-03 09:12

I\'m new to writing template metaprogramming code (vs. just reading it). So I\'m running afoul of some noob issues. One of which is pretty well summarized by this non-SO p

相关标签:
2条回答
  • 2020-12-03 09:53

    The way I see it you don't want SFINAE here. SFINAE is useful to pick between different templated overloads. Basically, you use it to help the compiler pick between template <typename Pointer> void f(Pointer); and template <typename NotPointer> void f(NotPointer);.

    That's not what you want here. Here, you have two functions with different names, not two overloads of the same. The compiler can already pick between template <typename Pointer> void f(Pointer); and template <typename NotPointer> void g(NotPointer);.

    I'll give an example to explain why I think SFINAE is not only unnecessary, but undesirable here.

    Foo<int> not_pointer;
    Foo<int*> pointer;
    
    not_pointer.valid_if_pointer(); // #1
    not_pointer.valid_if_not_pointer(); // #2
    pointer.valid_if_pointer(); // #3
    pointer.valid_if_not_pointer(); // #4
    

    Now, let's say you managed to get this working with SFINAE. Attempting to compile this piece of code will yield errors on lines #1 and #4. Those errors will be something along the lines of "member not found" or similar. It may even list the function as a discarded candidate in overload resolution.

    Now, let's say you didn't do this with SFINAE, but with static_assert instead. Like this:

    template <typename T>
    struct Foo {
        void valid_if_pointer(T) const {
            static_assert(std::is_pointer<T>::value, "valid_if_pointer only works for pointers");
            // blah blah implementation
        }
    
        void valid_if_not_pointer(T) const {
            static_assert(!std::is_pointer<T>::value, "valid_if_not_pointer only works for non-pointers");
            // blah blah implementation
        }
    };
    

    With this you'll get errors on the same line. But you'll get extremely short and useful errors. Something people have been asking of compiler writers for years. And it's now at your doorstep :)

    You get the same thing: errors on both cases, except you get a much better one without SFINAE.

    Also note that, if you didn't use static_assert at all and the implementation of the functions was only valid if given pointers or non-pointers, respectively, you would still get errors on the appropriate lines, except maybe nastier ones.

    TL;DR: unless you have two actual template functions with the same name, it's preferable to use static_assert instead of SFINAE.

    0 讨论(0)
  • 2020-12-03 10:01

    Firstly, C++11 did not carry forward boost's disable_if. So if you're going to transition boost code, you'll need to use enable_if with a negated condition (or redefine your own disable_if construct).

    Secondly, for SFINAE to reach in and apply to the method level, those methods must be templates themselves. Yet your tests have to be done against those templates' parameters...so code like enable_if<is_pointer<T> will not work. You can finesse this by making some template argument (let's say X) default to be equal to T, and then throw in a static assertion that the caller has not explicitly specialized it to something else.

    This means that instead of writing:

    template <typename T>
    struct Foo {
        typename enable_if<is_pointer<T>::value, void>::type
        valid_if_pointer(T) const { /* ... */ }
    
        typename disable_if<is_pointer<T>::value, void>::type
        valid_if_not_pointer(T) const { /* ... */ }
    };
    

    ...you would write:

    template <typename T>
    struct Foo {
        template <typename X=T>
        typename enable_if<is_pointer<X>::value, void>::type
        valid_if_pointer(T) const {
            static_assert(is_same<X,T>::value, "can't explicitly specialize");
            /* ... */
        }
    
        template <typename X=T>    
        typename enable_if<not is_pointer<X>::value, void>::type
        valid_if_not_pointer(T) const {
            static_assert(is_same<X,T>::value, "can't explicitly specialize");
            /* ... */
        }
    };
    

    Both are now templates and the enable_if uses the template parameter X, rather than T which is for the whole class. It's specifically about the substitution that happens whilst creating the candidate set for overload resolution--in your initial version there's no template substitution happening during the overload resolution.

    Note that the static assert is there to preserve the intent of the original problem, and prevent someone being able to compile things like:

    Foo<int>().valid_if_pointer<int*>(someInt);
    
    0 讨论(0)
提交回复
热议问题