模板专业化与模板参数

时间:2017-11-07 15:52:35

标签: c++ c++11 templates template-specialization c++98

我们假设有一个templateFoo

template <typename T>
class Foo {
  void foo();
};

我有另一个templateBar(独立于第一个):

template <int N>
class Bar {};

让我们说,我想专门针对任何foo()类的Bar方法。 我错写了:

template <>
template <int N>
void Foo<Bar<N> >::foo() { /* ... */ }

编译器指责我,因为类型不完整:

error: invalid use of incomplete type 'class Foo<Bar<N> >'
 void Foo<Bar<N> >::foo() { }

Code

我正在使用 C ++ 98 ,但我想知道 C ++ 11 中是否存在不同的解决方案。

注意

我可以解决专门针对通用Foo的整个班级Bar的问题,但在我必须定义所有方法之后。

Example Code

这不是我想要的,我正在寻找(如果存在)更优雅的解决方案(包括C ++ 98和C ++ 11),它允许我专门化并实现一个单独的类方法。 / p>

编辑:

The question on SO没有解释如何专门使用模板参数。实际上,我的问题显示了编译器如何抱怨这一点。

4 个答案:

答案 0 :(得分:3)

对于C ++ 11,您可以在非专业std::enable_if类中使用SFINAE启用/禁用(使用foo())两个不同版本的Foo

在C ++ 98中你没有std::enable_if但你可以模拟它(给我一些时间,我试着提出一个例子)。抱歉:我的想法没有因为这种方法需要对C ++ 11创新的方法使用默认模板参数。

另一种方法是为Foo()定义模板基类,比如说FooBase,在foo()中插入foo()(并且只有FooBase)并专门化{{ 1}}。

另一种方式,也适用于C ++ 98,可以是标签分派:你可以定义一个唯一的FooBase,参数为零,调用另一个foo(),参数确定foo()

以下是完整的(C ++ 98可编译)示例

T

答案 1 :(得分:0)

如果不需要公共基础,另一种方法可能是给foo()一个自定义点,例如特征:

template <typename T>
struct foo_traits;

template <typename T>
struct Foo {
  void foo(){ foo_traits<T>::foo_cp(*this); }
};

template <typename T>
struct foo_traits{ static void foo_cp(T&){/*default*/} };

template <int N>
class Bar {};

template <int N>
struct foo_traits<Bar<N>>{ static void foo_cp(Foo<Bar<N>>&){/*spec*/} };

这样的特性也可以是一个实现细节的朋友,如果它的唯一目的是在内部为Bar提供foo()特化。

答案 2 :(得分:0)

如果您不能专门化foo,请对其进行定义,以便将调用委托给内部 foo-implementation 类。然后把这个班专门化。
像这样的东西应该在C ++ 98中编译,它与原始代码没什么不同:

template <typename T>
class Foo {
    template<typename>
    struct FooImpl;

public:
    void foo() { FooImpl<T>()(); }
};

template <int N>
class Bar {};

template <typename T>
template <int N>
struct Foo<T>::FooImpl< Bar<N> > {
    void operator()() { /* ... */ }
};

int main() {
    Foo< Bar<0> > fb;
    fb.foo();

    Foo<int> fi;
    //fi.foo();
}

最后一行没有按预期编译(至少我得到的是预期的结果,只是为FooImpl定义函数调用运算符)。

通过这种方式,您可以有选择地定义您希望foo工作的专精。在所有其他情况下,尝试使用foo将导致编译错误。

答案 3 :(得分:0)

  

我想知道 C ++ 11 中是否存在不同的解决方案。

这是标记式调度的经典用例,max66已经建议过。这种方法甚至语法在C ++ 98和C ++ 11中基本相同。

我认为这是一个比max66更清洁的实现(running on godbolt):

template <class T>
class Foo {
    template <class>
    struct tag{};
    template<class U>
    void foo_helper(tag<U>){std::cout << "default\n";}
    void foo_helper(tag<Bar<3> >){std::cout << "specialization for Bar<3>\n";}
public:
    void foo(){return foo_helper(tag<T>());}
};

原则是一样的;不接受任何参数的客户端函数调用辅助函数,该函数根据T参数构造一个空类型。然后正常的重载可以解决剩下的问题。

只有在这里我使用模板化的全包方法。

在C ++ 11中,语法只会略有改变;我们可以说tag<Bar<3>>而不是tag<Bar<3> >,因为新的解析规则允许嵌套模板使用V形。

我们还可以将标记和模板化的foo_helper全部变为variadic templates更加通用:

template <class T>
class Foo {
    template <class...>
    struct tag{};
    template<class... U>
    void foo_helper(tag<U...>){std::cout << "default\n";}
    void foo_helper(tag<Bar<3>>){std::cout << "specialization for Bar<3>\n";}
public:
    void foo(){return foo_helper(tag<T>{});}
};

C ++ 17 中实际开始变得非常有趣,引入了constexpr if,允许我们根据T编写看似普通分支逻辑的内容({ {3}}):

template <class T>
class Foo {
public:
    void foo(){
        if constexpr (std::is_same_v<T, Bar<3>>){std::cout << "Specialization for Bar<3>\n";}
        else std::cout << "default\n";
    }
};

正如您所看到的,所有标记内容都消失了,支持使用简单的if语句。

我们利用C ++ 11中引入的Live Demo来检查T的类型与我们想要的类型。这样的事情以前不一定有效,因为需要编译所有分支。在C ++ 17中,只编译了选中的分支(在编译时)。

请注意,您可以使用type_traitstypeid)在C ++ 98中尽早模拟此行为:

void foo(){
    if (typeid(T) == typeid(Bar<3>)){std::cout << "Specialization for Bar<3>\n";}
    else std::cout << "default\n";
}

然而,typeid方法选择不当有两个原因:

  1. 这是我们在编译时知道的信息的运行时检查(慢)
  2. 它很脆弱,因为所有分支都必须为所有模板实例化进行编译,而在C ++ 17 if constexpr中只编译所选的分支。