【问题标题】:Specializations only for C++ template function with enum non-type template parameter仅适用于具有枚举非类型模板参数的 C++ 模板函数
【发布时间】:2014-03-28 19:08:29
【问题描述】:

这个问题与this one 相关,只是我尝试使用枚举非类型模板参数,而不是处理类型名模板参数。

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

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

背景: 我有一个类成员函数,它接受一个枚举“ArgType”和一个名称。

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

定义已变成if..else..if..else 列表,用于六种左右允许的 ArgType 案例。我还必须有最后一个案例,它会为不允许的 ArgType 抛出异常。我认为将 ArgType 移动到模板参数并为每个允许的 ArgType 提供专业化会更干净。误用会在编译时被发现。

【问题讨论】:

  • 为什么你更喜欢枚举标记而不是类型本身,或者基于类型的标记?
  • 我选择的 T_INTT_DOUBLE 是我现在意识到的糟糕的坏名字。我会将其更改为与真实类型不太混淆的东西,因为这是我的实际使用兴趣。
  • 这个想法是能够注释掉 add_one_third 的第二次调用并且编译应该成功。它没有。

标签: c++ enums template-specialization static-assert


【解决方案1】:

类内部结构的部分特化:

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

【讨论】:

  • 看起来很有希望。为什么应该失败的调用会导致编译器错误error: implicit instantiation of undefined template 'foo_class::AddOneThird&lt;1, double&gt;',而我认为这会导致我能够得到相同的链接丢失定义情况?
  • @NoahR 类似于类的前向声明:如果使用前向声明的类,则会出现编译时错误。
  • 我无法让这个想法适用于更现实的用例:main() 在一个源文件中,foo_class 在头文件中声明,apply() 在单独的 cpp 源中定义文件。你能去掉typename T模板参数吗?我的问题没有必要,并且使问题变得混乱。
  • @NoahR 不允许完全特化嵌套类,因此typename T
  • 好的,我能够将apply() 的定义移动到单独的 C++ 源文件中。是否也可以将 struct AddOneThird&lt;T_DOUBLE,T&gt; 的部分专业化从标题中提取出来?它给只与实现相关的头文件增加了很多噪音。
【解决方案2】:

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

【讨论】:

  • 这个想法是能够注释掉 add_one_third 的第二次调用并且编译应该成功。它没有。
  • 检查你的 boost 静态断言。请发布您的编译器输出
  • 是的,一个可能的问题点,但我不知道如何做出断言。但更有可能的是(禁用 CAT 行),甚至不应该进行静态断言,但确实如此。
  • 正确。但取消注释您的 CAT 行。这编译并运行并输出“YES”。期望的行为是它不会编译,因为 CAT 不是允许的情况。
  • 看来我错了,模板专业化确实会即时添加方法,我很抱歉。我正在搜索标准以对此进行解释,稍后会回复您。同时,查看amazing article on the subject by Herb Sutter
【解决方案3】:

枚举案例和使用类型名参数的引用问题之间的主要区别在于,默认定义将被编译以供任何使用。因此,一个可行的解决方案就像修改 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!\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;
}

当然,整个设计闻起来很糟糕,并且使用 AllowedTypes 作为具有继承特化的struct/class 可能会更优雅地处理。但这涉及到手头的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 2018-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多