专门化仅适用于带有enum非类型模板参数的c++模板函数



这个问题与这个问题有关,只是我没有处理typename模板参数,而是尝试使用enum非类型模板参数。

在非类型模板形参的情况下,是否可能有一个模板化的(类成员函数)只有特化,没有一般的(工作的)定义?

  1. 我能够得到一个版本的工作,通过在类体中声明,只提供专门化,但任何误用一个未定义的模板参数调用不会产生错误,直到链接。更糟糕的是,缺少的符号神秘地指的是枚举的整数值,而不是它的名称,所以这会让其他开发人员感到困惑。

  2. 我能够从参考问题中获得BOOST_STATIC_ASSERT技术,仅用于typename模板参数。

下面的代码演示了这个想法。我不希望CAT -version调用编译:

#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!nn";
    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;
}
背景:

我有一个类成员函数,它接受一个enum "ArgType"和一个名称。

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

这个定义已经变成了一个if..else..if..else列表,包含了半打左右允许的ArgType情况。我还必须有一个final case,为不允许的ArgType抛出异常。我认为将ArgType移动到模板参数会更干净,并为每个允许的ArgType提供专门化。

类内部结构的局部专门化:

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

使用(友类)的完全特化:

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

利用c++ 11或提高::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

专门化函数模板要直观得多。首先,你不能把它们部分地专门化——这很大程度上只是因为语言说你不能。[2]另一方面,函数模板特化不会重载。这意味着您编写的任何专门化都不会影响使用哪个模板,这与大多数人直观地期望的相反。毕竟,如果您编写了具有相同签名的非模板函数,而不是函数模板专门化,则总是选择非模板函数,因为它总是被认为比模板更匹配。

如果你在写一个函数模板,最好把它写成一个单一的函数模板,永远不要专门化或重载,并完全按照类模板来实现函数模板。这是众所周知的间接层次,它引导您很好地避开函数模板的限制和阴暗角落。这样,使用模板的程序员将能够部分专门化和显式专门化类模板,而不会影响函数模板的预期操作。这既避免了函数模板不能部分专门化的限制,也避免了函数模板专门化不会重载这一有时令人惊讶的结果。问题解决了。

枚举类型sizeof不为0,至少将其更改为4。否则这将不起作用。枚举元素的大小不为0。

没有这个,一切都在运行

#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!nn";
    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;
}

枚举情况与使用typename形参的引用问题之间的主要区别在于,将为任何用途编译默认定义。因此,一个有效的解决方案就像修改BOOST_STATIC_ASSERT条件来检查允许的枚举值一样简单。

#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!nn";
    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;
}

当然,整个设计很糟糕,可以更优雅地处理AllowedTypes作为继承专门化的struct/class。但这就引出了问题

相关内容

  • 没有找到相关文章

最新更新