Search code examples
c++g++clang

Weird warning with SFINAE conversion function


This is my sample code:

#include <type_traits>

template <bool B>
struct S {
    // #1
    // template <bool C = B, class = std::enable_if_t<!C>>
    // operator S<true>() const { return {}; }

    // #2
    // operator S<true>() const { return {}; }
};

int main() {
    S<true> b1;
    S<false> b2;
}

When compiling with g++ (https://godbolt.org/z/P46qE8EGG) I get no warning whether I enable option #1, option #2, or both. I would expect a warning that it is a self conversion function when I enable #2, but maybe g++ doesn't have such a warning.

The real issue is when compiling with clang. I get the following warnings only if I enable #1:

error: conversion function converting 'S' to itself will never be used [-Werror,-Wclass-conversion]

Enabling #2 does not trigger this warning, and disabling the instantiation of either b1 or b2 does not prevent it. What is going on? Is this an error in clang?

EDIT: I am referring specifically to why #1 produces the warning. The other info was meant to show my attempts at trying to narrow the problem to a smaller working example


Solution

  • I would expect a warning that it is a self conversion function when I enable #2, but maybe g++ doesn't have such a warning.

    Correct, one can hope for compiler warnings (non-standard best practices), but there is no guarantee that a certain compiler provides them.

    Enabling #2 does not trigger this warning, and disabling the instantiation of either b1 or b2 does not prevent it. What is going on? Is this an error in clang?

    Clang is correct to not warn for #2, as it may be invoked, shown easier in an example with deleted definitions:

    template <bool B>
    struct S {  
        // #2
        operator S<true>() const = delete;
    };
    
    int main() {
        S<false> const b1;
        S<true> b2 = b1;  // error: call to a deleted function
    }
    

    Why? Since the implicit copy constructor copies

    S<true>  -> S<true>
    S<false> -> S<false>
    

    whereas however S<false> into S<true> is not a copy operation in the same sense.

    The template constructor is also present when the "copy from" class is S<false>, so Clang may seem to, at first glance, to incorrectly warn that this function will never be used:

    #include <type_traits>
    
    template <bool B>
    struct S {
        // #1 - warning: -Wclass-conversion
        template <bool C = B, class = std::enable_if_t<!C>>
        operator S<true>() const = delete;
    };
    
    int main() {
        S<false> const b1;
        S<true> b2 = b1;  // error: deleted function
    }
    

    However, Clang is likely not smart enough to analyze the SFINAE, and we can see the SFINAE construct is a red herring for when or when not the warning is emitted, as we get the same diagnostic in the following case:

    template <bool B>
    struct S {
        // #1 - warning: -Wclass-conversion
        template <typename Dummy = void>
        operator S<true>() const = delete;
    };
    
    int main() {
        S<false> const b1;
        S<true> b2 = b1;  // error: deleted function
    }
    

    We realize that Clang emits the warnings for when we instantiate S<true>, for which case it is actually true: the function will never be invoked.

    template <bool B>
    struct S {
        // #1
        template <typename Dummy = void>
        operator S<true>() const = delete;
    };
    
    S<false> sf{}; // correct: no warning emitted for #1
    S<true>  st{}; // correct: warning emitted for #1
    

    At this point we may wonder why then it doesn't emit a warning for #1. It could be that a template function (#1) ranks one step lower in overload resolution, or that analysis of the non-dependent non-template function (#2) happens only once, independent of enclosing the class template specializations' instantiations.

    If we make sure the function is never converting-to-self for any instantiation, the warnings, as expected, disappears:

    template <bool B>
    struct S {
        // #1
        template <typename Dummy = void>
        operator S<!B>() const = delete;
    };
    
    S<false> sf{}; // no warning emitted for #1
    S<true>  st{}; // no warning emitted for #1
    

    I wouldn't be surprised if Clang rejected a bug report for this issue, as the warning is accurate for the specialization which it flags it for, unless some heroic effort would go into understanding when and when not a certain template function is SFINAE:d or not.