Search code examples
c++templatestemplate-specialization

template specialization in C++ using enable_if


How template specialization works in this case in C++20:

#include <iostream>

template <typename T, typename E> void AFuncToTest() 
{
    std::cout << "it fails!" << std::endl;
}

template <typename T, std::enable_if_t<std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t>>> void AFuncToTest()
{
    std::cout << "int value!" << std::endl;
}

int main() 
{
    AFuncToTest<int, int8_t>();
}

OUTPUT: "it fails!"

Why the code is printing it fails! since I defined int8_t e.g.??

The AFuncToTest<int, int8_t>(); doesnt match: template <typename T, std::enable_if_t<std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t>>> void AFuncToTest()

If I change the way the algorithm is done it also doesnt work like this:

#include <iostream>

template <typename T, typename E> void AFuncToTest() 
{
    std::cout << "it fails!" << std::endl;
}

template <typename T, int8_t> void AFuncToTest()
{
    std::cout << "int value!" << std::endl;
}

int main() {
    AFuncToTest<int, int8_t>();
}

OUTPUT: "it fails!"

So I think I'm missing some concept of the templates that is not matching the right result


Solution

  • It looks like you are trying to activate function AFuncToTest when its template argument T is either int8_t or int16_t. I say this, because the condition you supply tests only parameter T. It does not test parameter E.

    // from the OP:
    std::enable_if_t<std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t>>
    

    If that is true, then you do not need template parameter E. Parameter T, alone, is sufficient. Parameter E was apparently intended to capture the result of "enable_if_t", but it is not needed.

    The following example uses function overloading, rather than function template specialization. The enable_if_t parameters it contains allow you to "turn on" or "turn off" the competing overloads.

    When the condition in an enable_if_t parameter evaluates to false, enable_if_t becomes malformed, and the overload using it will not compile. Due to SFINAE, however, the failure is ignored (and does not trigger a compilation error), while the overload is discarded. See CppReference.

    // main.cpp
    #include <cstdint>
    #include <iostream>
    #include <type_traits>
    
    // This is a "variable template."
    template< typename T >
    inline constexpr bool is_int8_or_int16 
        = std::is_same_v<T, std::int8_t> || std::is_same_v<T, std::int16_t>;
    
    // Note the "logical not" operator (i.e., the exclamation point).
    template< typename T, std::enable_if_t<!is_int8_or_int16<T>, bool> = true >
    void AFuncToTest()
    {
        std::cout << "it fails!" << std::endl;
    }
    
    // There is no "logical not" here.
    template< typename T, std::enable_if_t<is_int8_or_int16<T>, bool> = true >
    void AFuncToTest()
    {
        std::cout << "int value!" << std::endl;
    }
    
    int main()
    {
        AFuncToTest<int>();          // outputs "it fails!"
        AFuncToTest<std::int8_t>();  // outputs "int value!"
    }
    // end file: main.cpp
    

    Output:

    it fails!
    int value!
    

    Constraints to the rescue

    In a comment below, @Jarod42 points out that the OP is using C++20, and provides the following solution that is valid in C++20 (and later). This solution uses a requires clause, to place a constraint on template parameter T.

    // main.cpp 
    // Solution by @Jarod42
    #include <iostream>
    #include <type_traits>
    
    template <typename T> void AFuncToTest()
    {
        std::cout << "it fails!" << std::endl;
    }
    
    template <typename T>
        requires (std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t>)
    void AFuncToTest()
    {
        std::cout << "int value!" << std::endl;
    }
    
    int main()
    {
        AFuncToTest<int8_t>();
        AFuncToTest<int32_t>();
    }
    // end file: main.cpp
    

    As above, this works through function overloading.

    • When the constraint is not satisfied, the function definition where it appears becomes malformed, and drops out of the overload set.
    • Otherwise, when the constraint is satisfied, the overload set will consist of two functions named AFuncToTest. Since the other criteria for selecting an overload are equal (in the case of function AFuncToTest), the compiler chooses the overload that is "more" constrained.

    The notion of "more" constrained has a precise definition which involves the idea of one constraint subsuming another. In most cases, however, all you need is an intuitive understanding to know whether a given overload is "more" constrained than another.