Specializations only for C++ template function with enum non-type template parameter

12,122

From Herb Sutter

It's a lot less intuitive to specialize function templates. For one thing, you can't partially specialize them -- pretty much just because the language says you can't.[2] For another thing, function template specializations don't overload. This means that any specializations you write will not affect which template gets used, which runs counter to what most people would intuitively expect. After all, if you had written a nontemplate function with the identical signature instead of a function template specialization, the nontemplate function would always be selected because it's always considered to be a better match than a template.

If you're writing a function template, prefer to write it as a single function template that should never be specialized or overloaded, and implement the function template entirely in terms of a class template. This is the proverbial level of indirection that steers you well clear of the limitations and dark corners of function templates. This way, programmers using your template will be able to partially specialize and explicitly specialize the class template to their heart's content without affecting the expected operation of the function template. This avoids both the limitation that function templates can't be partially specialized, and the sometimes surprising effect that function template specializations don't overload. Problem solved.

Your enum type sizeof is not 0, change that to 4 at least. Otherwise this will not work. A enum element size is not 0.

Without that everything runs

#include <iostream>

struct foo_class
{
    enum AllowedTypes { DOG, CAT };

    template <AllowedTypes type>
    void add_one_third( double bar ) const
    {
        std::cout << "YES" << std::endl;
    }
};

template<>
void foo_class::add_one_third<foo_class::DOG>( double bar ) const
{
    std::cout << "DOG specialization: " << bar + 1./3. << std::endl;
}

int main()
{
    std::cout << "Template Specialization!\n\n";

    foo_class a;
    a.add_one_third<foo_class::DOG>(3.0); // should succeed
    // Compilation fails with or without the following line:
    //a.add_one_third<foo_class::CAT>(3.0); // should fail at compile-time

    return 0;
}
Share:
12,122
NoahR
Author by

NoahR

Updated on June 25, 2022

Comments

  • NoahR
    NoahR almost 2 years

    This question is related to this one except that rather than dealing with typename template parameters, I am trying to use an enum non-type template parameter.

    Is it possible to have a templated (class member function) with only specializations, no general (working) definition in the case of non-type template parameter?

    1. I was able to get one version working, by declaration in the class body and providing specializations only, but any misuse calling with a non-defined template parameter doesn't produce an error until linking. What's worse is the missing symbol cryptically refers to the enum's integral value and not its name, so it would be confusing to other developers.

    2. I was able to get the BOOST_STATIC_ASSERT technique from the referenced question to work for typename template parameter only.

    This code demonstrates the idea. I don't want the CAT-version call to compile:

    #include <iostream>
    #include <boost/static_assert.hpp>
    
    // CLASS HEADER FILE:
    struct foo_class
    {
        enum AllowedTypes { DOG, CAT };
    
        template <AllowedTypes type>
        void add_one_third( double bar ) const
        {
            BOOST_STATIC_ASSERT_MSG(sizeof(type)==0, "enum type not supported.");
        }
    };
    
    // CLASS SOURCE FILE
    template<>
    void foo_class::add_one_third<foo_class::DOG>( double bar ) const
    {
        std::cout << "DOG specialization: " << bar + 1./3. << std::endl;
    }
    
    
    // USER SOURCE FILE
    int main()
    {
        std::cout << "Template Specialization!\n\n";
    
        foo_class a;
        a.add_one_third<foo_class::DOG>(3.0); // should succeed
        // Compilation fails with or without the following line:
        a.add_one_third<foo_class::CAT>(3.0); // should fail at compile-time
    
        return 0;
    }
    

    Background: I have a class member function that takes an enum "ArgType" and a name.

    void declareKernelArgument( ArgType type, std::string name );
    

    The definition has turned into an if..else..if..else list for the half-dozen or so allowed ArgType cases. I also have to have final case that throws an exception for an not-allowed ArgType. I'm thinking it would be cleaner to move ArgType to a template parameter, and provide a specialization for each allowed ArgType. Misuse would be caught at compile-time.