模板模板参数和默认参数

4
考虑以下使用“模板模板”参数实例化类模板的代码,使用多个类型:
#include <iostream>
using namespace std;

enum E
{
    a = 0,
    b = 1
};

template <template <E> class Action, class T>
void do_something(const T& value)
{
    typedef Action<a> type1;
    typedef Action<b> type2;
}

template <E e, class Enable = void>
class Foo
{

};

int main()
{
    do_something<Foo>(int(55));
}

使用旧版本的编译器(GCC 4.1.2),上述代码可以正常编译。然而,如果使用新版编译器(GCC 4.4.6或4.8.1),将会产生以下错误:

test3.cpp:25:27: error: no matching function for call to ‘do_something(int)’
  do_something<Foo>(int(55));

看起来 GCC 无法绑定到 do_something,因为模板模板参数只声明了一个参数(枚举),但实际上 Foo 需要两个模板参数(尽管一个是默认的)。我想 GCC 4.1.2 允许忽略默认参数。
好的,如果我将模板定义更改为:
template <template <E, class> class Action, class T>
void do_something(const T& value)
{
    typedef Action<a> type1;
    typedef Action<b> type2;
}

如果我测试的GCC版本都无法编译它,那么所有的版本都会产生类似的错误:

test3.cpp:13: error: wrong number of template arguments (1, should be 2)
test3.cpp:10: error: provided for ‘template<E <anonymous>, class> class Action’

现在,编译器会抱怨表达式typedef Action<a> type1只提供了一个模板参数。显然,在这里我不能隐式地使用默认参数。

是否有一种方法可以在模板模板函数中使用模板的默认参数?


1
在你最终定义的 do_something 中,能否将第一个 class 替换为 class=void?这可能会告诉它 Action 是一个带有一个默认参数的双参数模板。我认为我希望这样工作,因为这意味着默认值被指定了两次! - Aaron McDaid
那真的起作用了。这符合标准吗? - Channel72
这只是我的猜测!我不知道标准。我使用的是g++-4.6.3,它对我有效。 - Aaron McDaid
@AaronMcDaid [temp.param]/14:“模板的模板参数允许具有默认的模板参数。”但是,这并不影响模板是否为有效的模板模板参数(它并不真正有用,实时示例)。 - dyp
在我的实验中,对于 g++-4.6.3,任何在 do_something 中指定的默认值都优先于在 Foo 中指定的默认值。这有点奇怪,即使它是标准,我也不喜欢它。是否有更简洁的方法来创建一个单参数模板,该模板是一个带有一个默认参数的双参数模板的别名?据我所知,C++11 中的新的 using 模板别名可能与此相关。 - Aaron McDaid
@Dyp,我同意这并不是太有用。理想情况下,我们希望有一个模板模板,它可以将任何只有一个非默认参数的模板作为其参数。然后我们就可以在你的实例中使用C<A>C<B>。但是,正如你所展示的那样,如果C允许第二个参数有默认值,则需要一个具有两个参数的模板。 - Aaron McDaid
2个回答

6

默认参数对于模板参数的参数是被忽略的。在n3337规范中,第[temp.arg.template]章节的第二段有一个例子:

template<class T> class A { /∗ ... ∗/ };
template<class T, class U = T> class B { /∗ ... ∗/ };
template <class ... Types> class C { /∗ ... ∗/ };
template<template<class> class P> class X { /∗ ... ∗/ };
template<template<class ...> class Q> class Y { /∗ ... ∗/ };
X<A> xa; // OK
X<B> xb; // ill-formed: default arguments for the parameters of a template argument are ignored
X<C> xc; // ill-formed: a template parameter pack does not match a template parameter
Y<A> ya; // OK
Y<B> yb; // OK
Y<C> yc; // OK

请注意上面X<B> xb;处的注释。恐怕我找不到规范文本。
您可以将此与函数相关联——默认参数也不是签名的一部分。如果尝试通过函数指针调用具有默认参数的参数的函数,也会发生同样的情况。

1
然而,显然你可以在模板模板参数中放置默认参数。 - Channel72
是的,显然 - 但这与那个评论所说的不同。 - jrok

1
使用C++11中的新特性using模板别名,您可以创建一个一参数模板,它等价于另一个具有两个参数的模板,其中一个参数是默认值。
template <E e> using Foo1 = Foo<e>;

这里创建了一个一参数模板Foo1,即使Foo技术上有两个参数,其中一个是默认的。您可以按如下方式使用它:
do_something<Foo1>(int(55));

或者,如果不支持C++11特性(例如using),则可以在声明do_something时指定默认值。这意味着,不幸的是,do_something无法再处理简单的单参数模板。因此,我认为上面的using方法更好。

template <template <E, class = void> class Action, class T>
void do_something(const T& value);

如果您采用这种方法,将默认值放在do_something的参数中,则该默认值优先于在声明Foo时指定的默认值。这是基于我的实验,我不能自信地评论什么是标准的,什么不是标准的。但我认为使用using技巧完全符合C++11的标准。(Ubuntu clang版本3.0-6ubuntu3)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接