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

旧时模样 提交于 2019-12-08 14:33:26

With partial specialization of a structure inside the class:

#include <iostream>

class foo_class
{
    public:
    enum AllowedTypes { T_DOUBLE, T_INT };

    private:
    template <AllowedTypes type, typename T>
    struct AddOneThird;

    template <typename T>
    struct AddOneThird<T_DOUBLE, T> {
        static void apply(T bar) {
            std::cout << "T_DOUBLE specialization: " << bar + 1.0/3.0 << std::endl;
        }
    };

    public:
    template <AllowedTypes type>
    void add_one_third( double bar ) const {
        AddOneThird<type, double>::apply(bar);
    }
};

int main() {
    foo_class a;
    a.add_one_third<foo_class::T_DOUBLE>(3.0);
    // error: incomplete type ‘foo_class::AddOneThird<(foo_class::AllowedTypes)1u
    // a.add_one_third<foo_class::T_INT>(3.0); // should fail at compile-time

    return 0;
}

With full specialization of a (friend) class:

#include <iostream>

class foo_class
{
    public:
    enum AllowedTypes { T_DOUBLE, T_INT };

    // if needed
    // template<AllowedTypes> friend struct AddOneThird;

    public:
    template <AllowedTypes type> void add_one_third( double bar ) const;
};

template <foo_class::AllowedTypes>
struct AddOneThird;

template <>
struct AddOneThird<foo_class::T_DOUBLE> {
    static void apply(double bar) {
        std::cout << "T_DOUBLE specialization: " << bar + 1.0/3.0 << std::endl;
    }
};

template <foo_class::AllowedTypes type>
void foo_class::add_one_third( double bar) const {
    AddOneThird<type>::apply(bar);
}


int main() {
    foo_class a;
    a.add_one_third<foo_class::T_DOUBLE>(3.0);
    // error: incomplete type ‘AddOneThird<(foo_class::AllowedTypes)1u>’ used
    //        in nested name specifier
    //a.add_one_third<foo_class::T_INT>(3.0); // should fail at compile-time

    return 0;
}

Utilizing C++11 or boost::enable_if:

#include <iostream>
#include <type_traits>

class foo_class
{
    public:
    enum AllowedTypes { T_DOUBLE, T_INT };

    template <AllowedTypes type>
    typename std::enable_if<type == T_DOUBLE>::type
    add_one_third( double bar ) const {
        std::cout << "T_DOUBLE specialization: " << bar + 1.0/3.0 << std::endl;
    }
};

int main() {
    foo_class a;
    a.add_one_third<foo_class::T_DOUBLE>(3.0);
    // error: no matching function for call to ‘foo_class::add_one_third(double)’
    //a.add_one_third<foo_class::T_INT>(3.0); // should fail at compile-time
    return 0;
}

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;
}

The main difference between the enum case and the referenced question using a typename parameter is that the default definition will be compiled for any use. So, a working solution is as simple as modifying the BOOST_STATIC_ASSERT condition to check allowed enum values.

#include <iostream>
#include <stdexcept>
#include <boost/static_assert.hpp>

// CLASS HEADER FILE:
struct foo_class
{
    enum AllowedTypes { DOG, CAT, MOUSE };

    template <AllowedTypes type>
    void give_bath() const
    {
        // compile fails if ever attempting to use this function with CAT parameter.
        BOOST_STATIC_ASSERT_MSG( (type==DOG) || (type==MOUSE) , "enum type not supported.");
        throw std::runtime_error("Unexpected. Above list inconsistent with specializations.");   
    }
};

// CLASS SOURCE FILE
template<>
void foo_class::give_bath<foo_class::DOG>() const
{
    std::cout << "DOG is bathed." << std::endl;
}

template<>
void foo_class::give_bath<foo_class::MOUSE>() const
{
    std::cout << "MOUSE is bathed." << std::endl;
}


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

    foo_class a;
    a.give_bath<foo_class::DOG>(); //success
    a.give_bath<foo_class::MOUSE>(); // success
    // Compilation fails with the following line:
    //a.give_bath<foo_class::CAT>(); // fails at compile-time as intended.

    return 0;
}

Of course, the whole design smells bad and could likely be handled more elegantly with AllowedTypes being a struct/class with inherited specializations. But this gets to the question at hand.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!