【问题标题】:Why does C++ forbid this partial specialization?为什么 C++ 禁止这种部分特化?
【发布时间】:2021-09-17 17:54:27
【问题描述】:

为什么 C++ 禁止这种部分特化?

这种禁忌背后的哲学是什么,我可以接受吗?
如果没有这种禁止,编程会容易得多。

模板应防止冗余。
现在我必须为 int d = 3 的每个类 N 产生冗余。

//__________________________________________
//__________________________________________
//
template<class N, int d>
class MyClass
{
public:
    void doo();
};

//__________________________________________
//__ allowed _______________________________
//
template<class N, int d>
void MyClass<N, d>::doo()
{
    cout << "general";
}

//__________________________________________
//__ forbidden _____________________________
//
template<class N>  
void MyClass<N, 3>::doo()
{
    cout << "partial specialization";
}

【问题讨论】:

  • 您正在尝试部分专门化一个函数。 C++ 只允许类的部分特化。至于基本原理,大概是因为您可以使用部分特化的类来执行函数可能具有的任何操作,并且当部分特化仅限于类时,该语言变得更容易编译。
  • 使用 C++17,您可以在 doo 的实现中添加一个 if constexpr (d == 3),作为“专门化”单个函数的一种方式
  • @UnholySheep:对不起,我不能接受这个答案。模板应提供干净的编码,并且通过这种解决方法,我的代码变得不干净。臃肿。


    不过还是谢谢你的回答。
  • @MBastieK 好吧,我可能遗漏了一些东西,但我不确定你怎么会认为answer 中的代码比其他代码更多 臃肿。我猜这是见仁见智的问题。
  • 哲学? stackoverflow.com/q/13923684/817643 - 您要求 implicit 部分专业化。这为已经可以self immolate 的语言添加了油。

标签: c++ templates template-specialization partial-specialization


【解决方案1】:

您首先需要对课程本身进行部分专业化。它是一个模板类。它的成员函数是一个非模板函数。类的部分特化可以在定义上有所不同。也就是说,它们可以有不同的成员集。

例如

template<class N, int d>
class MyClass
{
public:
    void doo();
};

template<class N, int d>
void MyClass<N, d>::doo()
{
    std::cout << "general";
}

template<class N>
class MyClass<N, 3>
{
public:
    void doo();
};

template<class N>  
void MyClass<N, 3>::doo()
{
    std::cout << "partial specialization";
}

对于函数模板,它们有自己的函数重载和函数特化机制。

【讨论】:

  • 感谢您的回答。 (我经常偶然发现这一点,我过于依赖专业化)。我一直想知道是否有办法避免重复(基本上)MyClass&lt;N, 3&gt; 的实现,在您的情况下,这通常看起来与MyClass&lt;N, d&gt; 非常相似。也许有一种使用继承的方法(用于组合)。你知道这样的技术吗?
  • 来自莫斯科的@Vlad:如果头等舱有更多内容并且我不想继承的话,头等舱的专业化会产生很多冗余。我不在乎,如果编译器用我的第一个解决方案生成一个新的专用完整类。这就是模板的本质。我在stackoverflow中问这个问题的原因是,因为我在模板中遇到了递归积分模板成员函数的问题.class。但现在就太多了。在我看到逻辑原因或哲学之前,我不接受我在最初的问题中的第一个解决方案的禁令,直到现在我才知道。
【解决方案2】:

您可以先对类进行部分特化,然后为特化类实现方法:

#include <iostream>

template<class N, int d>
class MyClass
{
public:
    void doo();
};

template<class N>
class MyClass<N, 3>
{
public:
    void doo();
};

template<class N, int d>
void MyClass<N, d>::doo()
{
    std::cout << "general\n";
}

template<class N>
void MyClass<N, 3>::doo()
{
    std::cout << "partial specialization\n";
}

int main()
{
    MyClass<int, 1> o1{}; o1.doo();
    MyClass<int, 3> o3{}; o3.doo();
}

// Outputs
//     general
//     partial specialization

Demo

【讨论】:

  • 如果第一堂课有更多内容怎么办?如果我不想继承,是否必须复制粘贴?我只是想了解它背后的哲学或规则。现在我在我的第一个解决方案中没有看到任何问题。编译器可以用我的第一个解决方案组合(组装)一个专门的类。
  • @MBastieK:请注意,MyClass&lt;N, 3&gt;::doo() 的答案中的代码与您的问题中的代码完全相同....编译器无法判断您是否声明了部分非特化类的特化成员函数,部分特化类的成员函数,完全特化类的部分特化成员函数,部分特化类的部分特化成员函数......
  • 只有类才能部分特化的语言规则打破了这种歧义。
  • @BenVoigt “编译器无法判断是否......”我看到了方法。而且我没有看到歧义。但也许我必须更详细地理解你的答案或者有一个更复杂的项目。但是我的初始问题代码可以组装成一个完整的专业类。
  • @rturrado “现在你有两个具体的类,A 和 B,”我已经明白了,“为什么不从基类继承。”我也明白。就像我说的,我想了解哲学或规则。我现在对解决方法不感兴趣。不过谢谢。看到汇编代码有一件好事。谢谢你。
【解决方案3】:

函数的部分特化是一件非常棘手的事情,特别是如果你允许函数重载。

它可能导致无法特化的函数,或在意外重载时发生特化,或者永远无法调用特化。

我最好的建议是远离功能专业化。

对于你的问题,我想说,最好的办法是简单地使用 C++17 if constexpr:

template<class N, int d>
void MyClass<N, d>::doo()
{
    if constexpr (d != 3) {
        cout << "general";
    } else {
        cout << "only when d == 3";
    }
}

【讨论】:

  • 这使我的代码膨胀。我认为模板也适用于干净的编码。但感谢您的回答。 “它可能导致不可特化的函数,或在意外过载时发生的特化,或者永远无法调用特化。”这听起来不像是一条规则,更像是一种主观判断。这种禁忌背后是否有严格的规则或哲学?编译器可以用我的第一个解决方案组合(组装)一个专门的类。
  • @MBastieK 我不确定你的代码膨胀是什么意思。视觉上?那是主观的,专业化对我来说看起来很臃肿。膨胀二进制?不,它没有,因为分支是在编译时完成的。 This sound less like a rule and more like a subjective judgement 好吧,我列出了允许部分专业化函数的后果。委员会和编译器实现者认为语言的混乱和复杂性是不值得的,因为其他东西可以以更简单的方式很好地解决问题。
  • Guillaume Racicot 是的,主观的。我遵循 Robert C. Martin 的哲学。 The committee and compiler implementers decided that the confusion and complication of the language was not worth it 是的,我想了解更多细微差别或差异化。 StoryTeller-UnslanderMonica 给了我一个大概深刻的答案,我会花时间阅读。谢谢。
【解决方案4】:

我过去经常依赖模式匹配,这使我经常受到语言的这种限制,对此我从未找到优雅实用的解决方案。 我想我本能地学会了避开这种设计,尽管我偶尔会偶然发现它。

我知道有四种解决方案:

  1. 现代解决方案 (if constexpr),(实用但不优雅的 IMO)。
  2. 经典解决方案(在现实世界中不实用)。
  3. 经典方案 + CRTP(实用但优雅?)
  4. “深奥”的解决方案,enable_if(不是优雅的 IMO)

这篇文章主要阐述了数字 3。

这些解决方案都不是 100% 让我满意的。 我看不出语言在遇到专门的成员函数声明时不能只专门化模板类的根本原因,至少有限制。


现代解决方案(显示在一个答案中)是在通用版本中使用if constexpr。 我觉得这像是作弊,它并不优雅,我同意你的观点,必须有一种方法可以在模板中做到这一点。


经典的解决方案当然也是其他答案中所显示的。 但是我认为它不实用,原因是MyClass&lt;N, 3&gt; 在其声明和实现中通常看起来与一般情况MyClass&lt;N, d&gt; 非常相似,因此必须重复MyClass 的整个实现对于每个案例(部分)专业。 这是我无法接受的。

例如,考虑一下如果你有一个更复杂的类会发生什么:

template<class N, int d>
class MyClass
{
public:
    void A() const{....}
    void B() const{....}
    .
    .
    .
    void Z() const{....}
    void doo(){std::cout << "general";}
};

不一定,但您可能还需要有很多重复的代码:

template<class N>
class MyClass<N, 3>
{
    void A() const{....}
    void B() const{....}
    .
    .
    .
    void Z() const{....}
    void doo();
};

我想不出最坏的 DRY(不要重复自己)违规情况。


我找到的解决方法是提取MyClass的绝对公共部分:

template<class MyClassCRTP>
class BasicMyClass<MyClassCRTP> // eventually we need will need to know the derived class
{
public:
    void A() const{....}
    void B() const{....}
    .
    .
    .
    void Z() const{....}
};

template<class N, int d>
class MyClass : BasicMyClass<MyClass<N, d>>
{
public:
    void doo(){std::cout << "general";}
};

template<class N>
class MyClass<N, 3> : BasicMyClass<MyClass<N, 3>>
{
    void doo();
};

现在您可以专门化MyClass&lt;N, 3&gt;::doo 而无需太多重复代码。

这真的很优雅吗?我不知道。 它也确实打开了其他的蠕虫罐头。 当然不理想,我们想专门化一个(方法)函数,结果我们得到了额外的类!


最后,为了完整起见,深奥的解决方案,并不比 if constexpr 好,但有点向后兼容 IMO:

template<class N, int d>
class MyClass
{
public:
    template<class Dummy, std::enable_if<d != 3 and sizeof(Dummy*), int> = 0>
    void doo(){std::cout << "general";}
    template<class Dummy, std::enable_if<d == 3 and sizeof(Dummy*), int> = 0>
    void doo(){std::cout << "special";}
};

【讨论】:

  • @alfC I agree with you that there must be a way to do this within templates. 谢谢。我最初的问题是模板类具有一个类积分模板参数,并且在递归模板函数中也具有另一个积分模板参数,并且我需要一个用于积分 = 0 的结束函数。没有类积分模板参数我没有问题.甚至可以使用具有不同函数实例化的相同对象实例化,我希望使用循环展开或更正确的递归展开,这可能可以称为剪裁(编译时)。
  • @alfC 附录:元编程中的元编程使用 'if constexpr' 破坏了我的模板原则。
猜你喜欢
  • 2013-05-10
  • 2017-09-11
  • 2011-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-04
  • 1970-01-01
相关资源
最近更新 更多