c++templatescompiler-errorslanguage-lawyertemplate-instantiation

Different behavior of function template instantiation using different compilers


Description

Here' a piece of code that is treated differently by clang, gcc and msvc compilers:

#include <type_traits>  

template <bool> struct enum_as_byte_check;
template <> struct 
[[deprecated("deprecated warning")]] enum_as_byte_check<true> {};

template <class T>
using is_scoped_enum = std::integral_constant<bool, !std::is_convertible<T,int>{}
                                                  && std::is_enum<T>{}>;
template<class EnumT>
class enum_as_byte
{
    typedef enum_as_byte_check<is_scoped_enum<EnumT>::value> Check;
};

    
template<typename EnumT>
static inline void bar(const EnumT)
{
}

template<typename EnumT>
static inline void bar(enum_as_byte<EnumT>)
{
    static_assert(false && "");
}

enum class Foo : int { };
enum Bar {};
static_assert(is_scoped_enum<Foo>::value);
static_assert(!is_scoped_enum<Bar>::value);

int main() 
{
    bar<Foo>(Foo{});
}

Results (links lead to corresponding Compiler Explorer setups):

  1. x86-64 clang 17.0.1:

    <source>:13:10: warning: 'enum_as_byte_check<true>' is deprecated: deprecated warning [-Wdeprecated-declarations]
    13 |         typedef enum_as_byte_check<is_scoped_enum<EnumT>::value> Check;
        |                 ^
    <source>:35:14: note: in instantiation of template class 'enum_as_byte<Foo>' requested here
    35 |     bar<Foo>(Foo{});
        |              ^
    <source>:35:5: note: while substituting deduced template arguments into function template 'bar' [with EnumT = Foo]
    35 |     bar<Foo>(Foo{});
        |     ^
    <source>:5:3: note: 'enum_as_byte_check<true>' has been explicitly marked deprecated here
        5 | [[deprecated("deprecated warning")]] enum_as_byte_check<true> {};
        |   ^
    

    1.1. if bar<Foo>(Foo{}); is commented: no warnings

  2. x86-64 clang 16.0.0:

    <source>:25:5: error: static assertion failed due to requirement 'false && ""'
        static_assert(false && "");
        ^             ~~~~~~~~~~~
    <source>:13:10: warning: 'enum_as_byte_check<true>' is deprecated: deprecated warning [-Wdeprecated-declarations]
            typedef enum_as_byte_check<is_scoped_enum<EnumT>::value> Check;
                    ^
    <source>:35:14: note: in instantiation of template class 'enum_as_byte<Foo>' requested here
        bar<Foo>(Foo{});
                ^
    <source>:35:5: note: while substituting deduced template arguments into function template 'bar' [with EnumT = Foo]
        bar<Foo>(Foo{});
        ^
    <source>:5:3: note: 'enum_as_byte_check<true>' has been explicitly marked deprecated here
    [[deprecated("deprecated warning")]] enum_as_byte_check<true> {};
    ^
    

    2.1. if static_assert is commented:

    <source>:13:10: warning: 'enum_as_byte_check<true>' is deprecated: deprecated warning [-Wdeprecated-declarations]
            typedef enum_as_byte_check<is_scoped_enum<EnumT>::value> Check;
                    ^
    <source>:35:14: note: in instantiation of template class 'enum_as_byte<Foo>' requested here
        bar<Foo>(Foo{});
                ^
    <source>:35:5: note: while substituting deduced template arguments into function template 'bar' [with EnumT = Foo]
        bar<Foo>(Foo{});
        ^
    <source>:5:3: note: 'enum_as_byte_check<true>' has been explicitly marked deprecated here
    [[deprecated("deprecated warning")]] enum_as_byte_check<true> {};
    ^
    

    2.2. if bar<Foo>(Foo{}); is commented and static_assert is not:

    <source>:25:5: error: static assertion failed due to requirement 'false && ""'
        static_assert(false && "");
        ^             ~~~~~~~~~~~
    

    2.3 if both bar<Foo>(Foo{}); and static_assert are commented:: no warnings

  3. x86-64 gcc 13.1/13.2: no warnings

  4. x86-64 gcc 12.3:

    <source>: In function 'void bar(enum_as_byte<EnumT>)':
    <source>:25:25: error: static assertion failed
    25 |     static_assert(false && "");
        |                   ~~~~~~^~~~~
    

    4.1. same output if bar<Foo>(Foo{}()); is commented

    4.2. no warnings if static_assert is commented

  5. x86 MSVC v19.latest: no warnings

Questions:


Solution

  • You've got too many things going on in that test case. Make a minimal example!

    The static_assert(false) thing is P2593 (C++23), which some compiler releases already support and some don't. We can ignore that.

    Your real question seems to be about the intended behavior of [[deprecated]] when it's applied to a specific specialization instead of to the primary template. Everyone agrees that the deprecation warning should be given for use of that specific specialization.

    GCC seems to give the deprecation warning only for uses of the deprecated specialization that aren't dependent on any template parameter (Godbolt:)

    template<class> struct A;
    template<> struct [[deprecated]] A<int> {};
    
    template<class T>
    void a() { A<T> t; }  // Clang warns (in phase 2), GCC never does
    
    template<class T>
    void b() { A<int> t; }  // Both warn (in phase 1)
    
    void c() { A<int> t; }  // Both warn
    
    template void a<int>();
    template void b<float>();
    

    Whether A<int> is "really used" or just used as an alias doesn't matter at all; using U = A<int> is still warned-about, even if U is never used after that. (Godbolt.)


    Finally, you asked:

    Does the enum_as_byte<EnumT> in the definition of bar count as a user-defined conversion or not?

    No, "[user-defined] conversion" is something that happens during evaluation; it's not a syntactic property. Since overload resolution never selects bar(enum_as_byte<EnumT>), no call to it ever happens, and so nothing ever gets converted to enum_as_byte<EnumT>.