Search code examples
c++template-meta-programmingsfinaeenable-if

Why does this substitution failure create an error, again?


I asked a question just before about why std::enable_if<false> cannot be used in SFINAE contexts, as in:

template <typename T, typename DEFAULTVOID = void>
struct TemplatedStruct {};

template <typename T>
struct TemplatedStruct<T, std::enable_if_t<false>> {}; // enable_if expression
// isn't dependent on template type, is always false and so is an error

However in the following example it is dependent on a template argument, but this also creates an error:

#include <type_traits>

template <typename value_t_arg>
struct underlyingtype 
{
    static inline constexpr bool bIsIntegralType = 
        std::is_integral_v<value_t_arg>;

    template <typename T, typename DEFAULTVOID = void>
    struct IsSpecialType {
        static inline constexpr bool bIsSpecialType = false;
    };
    
    template <typename T>
    struct IsSpecialType<T, std::enable_if_t<bIsIntegralType>> {
        static inline constexpr bool bIsSpecialType = true;
    };
    
    // This also creates an error, this is essentially the same as above
    template <typename T>
    struct IsSpecialType<T, std::enable_if_t<std::is_integral_v<value_t_arg>>> {
        static inline constexpr bool bIsSpecialType = true;
    };


};

int main()
{
    underlyingtype<int> g1; // Works
    underlyingtype<double> g2; // std::enable_if_t<false, void>:
                               // Failed to specialize alias template
}

In the first case of using std::enable_if_t<false> it fails to compile no matter what I instantiate. However in this other case underlyingtype<int> g1; works while when I instantiate it with a double it then fails to compile, which makes me think they're two different problems.

Edit: I should mention, this fails to compile with Visual Studio Community 2019 16.9.3.


Solution

  • // Failed to specialize alias template

    For one, there's no alias template in your code.¹ You're just delcaring bIsIntegralType to be exactly the same thing as std::is_integral_v<value_t_arg>, which is fixed (to false or true) as soon as the instantiation of underlyingtype takes place.

    Therefore, the two specializations

        template <typename T>
        struct IsSpecialType<T, std::enable_if_t<bIsIntegralType>> {
            static inline constexpr bool bIsSpecialType = true;
        };
    
        // This also creates an error, this is essentially the same as above
        template <typename T>
        struct IsSpecialType<T, std::enable_if_t<std::is_integral_v<value_t_arg>>> {
            static inline constexpr bool bIsSpecialType = true;
        };
    

    are the same thing, hence clang says

    Class template partial specialization 'IsSpecialType<T>' cannot be redeclared
    

    And this is independent of what value_t_arg you pass to underlyingtype.

    When removing either of the two identical specializations, the code is ok as regards underlyingtype<int> g1;, but it is still invalid upon trying to instantiate underlyingtype<double>, because value_t_arg is "blocked" to double in that case, which makes bIsIntegralType be just a false compile-time value, which in turns means that you're passing an always-and-ever-false to std::enable_if_v.

    Putting it in another way, when you ask for underlyingtype<double>, the compiler starts instantiating the class underlyingtype with value_t_arg = double; at this point the compiler hasn't even looked at IsSpecialType, but it knows that bIsIntegralType == false, which makes the code for IsSpecialType's specialization invalid as per the previous question.


    (¹) An alias template is a templated type alias,

    template <typename T>
    using new_name = old_name<T>;
    

    whereas in your code there's no using at all, so there couldn't be a type alias, let alone an alias template.


    Based on this and the previous question, it looks like you're trying to get into SFINAE and Template Meta-Programming. If I may give you a suggestion, a good way to learn it is to read and understand how the Boost.Hana library works. There's a lot of TMP and SFINAE there, but the quality of the code is high (imho) and the code itself is extremely well documented and, hence, understandable (obviously it takes time).