功能模板专业化-编译器仅“看到”基本模板,不会编译

时间:2018-10-07 11:49:19

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

我对功能模板专业化有疑问。我正在使用clang和C ++ 17,但这在较旧版本和gcc上应该显示相同的行为(据我所知)。我将其简化为以下示例:

代码

Test.h

#ifndef TEST_H
#define TEST_H

#include <iostream>
#include <memory>

class A { };
class B { public: B(std::string const& arg) { } };

class Test {
public:
    template<typename T>
    T foo() {
        std::cout << "foo<T>" << std::endl;
        return T();
    }
};

#endif //TEST_H

Test.cpp

#include "Test.h"

template<>
B Test::foo<B>() {
    std::cout << "foo<B>" << std::endl;
    return B("");
}

main.cpp

#include <iostream>
#include <memory>
#include "Test.h"

int main() {
    Test t;
    t.foo<A>();
    t.foo<B>();

    return 0;
}

期望的行为

Test的类Test.h中定义了一个函数模板,在Temp.cpp中有一个专门化的模板。如预期的那样,我希望将基本模板用于类A(以及我将在实际代码中使用的任何其他类),并将专门化用于类B

在此示例中,专业化将使用特定参数调用B的构造函数;由于B没有默认的构造函数,因此无法使用基本模板。

所需的输出将是:

foo<T>
foo<B>

问题

如果我尝试编译以上代码,则出现以下错误:

error: no matching function for call to ‘B::B()’

据我所知,这是因为编译器不会像我应该期望的那样在Test.cpp中看到我的专长。

尝试1

作为健全性检查,我向类B() {}添加了默认构造函数B。这为我提供了所需的输出,因此尽管编译器会要求它与基础模板一起使用,但实际上实际上是在使用 。但这似乎是一种非常肮脏的解决方法,对于我的实际代码来说不是一个选择,因为实际代码要复杂一些。

尝试2

我遇到的第一个建议的解决方案是建议将整个函数定义移到类声明之外的头文件Test.h中。 Test.cpp现在为空,并且在类声明之后将函数定义移至Test.h中。我尝试过,并遇到以下错误

Test.h:22: multiple definition of `B Test::foo<B>()`; [...]/Test.h:22: first defined here

我似乎无法摆脱。

尝试3和4

next proposed solution表示将特殊化的前向声明添加到头文件中。 Test.cpp现在与上面的示例相同,但是我在Test.h的类声明中添加了

template<>
B foo<B>();

现在我遇到以下错误:

error: explicit specialization in non-namespace scope ‘class Test’
template<>
         ^

error: template-id ‘foo<B>’ in declaration of primary template
    B foo<B>();
             ^

在最初的问题(如上链接)中,它就像在名称空间中一样起作用。 我见过类似的建议解决方案,有人建议将函数定义移到类声明(而不只是向前声明)中,但是尝试时会出现完全相同的错误。

尝试5

现在,我可以将基本模板和专业化的前向声明都移到类声明上方的名称空间中,

namespace TestNamespace {
    template<typename T>
    T foo() {
        std::cout << "foo<T>" << std::endl;
        return T();
    }

    template<>
    B foo<B>();
}

然后将类中的原始模板更改为

template<typename T>
T foo() {
    return TestNamespace::foo<T>();
}

,当然要更改Test.cpp以实现名称空间的前向声明专用化:

template<>
B TestNamespace::foo<B>() {
    std::cout << "foo<B>" << std::endl;
    return B("");
}

现在可以编译代码,并且在运行时,可以获得所需的输出。但是,在实际代码中,我想将模板设为私有而不是公开。通过这种名称空间解决方法,可以从任何地方访问它,我想避免这种情况。另外,我感到我应该在这里应该采取不同的方法,但是我不明白为什么。

问题

现在,我现在可能会采用最后一种方法,但是我仍然对此不太满意,还有一些问题:

  1. 是否有适当的解决方案,或者至少有一种解决方法可以解决我在尝试5中提到的问题?
  2. 我是否仍在“正确的道路”上,还是应该全部作为警告来避免功能模板的专业化?
  3. 出于好奇,如果有人知道,为什么在非命名空间范围内禁止显式专门化?

感谢您的帮助。请让我知道是否需要提供更多信息。在过去的两天里,我一直在寻找解决方案或解释,但没有找到任何答案,因此我很抱歉,如果以前曾问过这个问题,我找不到它。

0 个答案:

没有答案