给定重载函数
f1
:
void f1(int);
int f1(char);
还有一个带有成员模板的类模板
X
f
:
template<class T>
struct X
{
template<class U>
static void f(T(*p)(U));
};
编译器能够从函数类型的一部分解析
f1
:
X<int>::f(f1); // T = int (specified), U = char (deduced)
X<void>::f(f1); // T = void (specified), U = int (deduced)
但是,可变参数类模板
Y
两侧都有参数包:
template<class... T>
struct Y
{
template<class... U>
static void f(T(*...p)(U));
};
没有做同样的事情:
Y<int>::f(f1); // error
Y<void>::f(f1); // error
Y<int, void>::f(f1, f1); // error
注意参数包只在一侧也没关系:
template<class... T>
struct Z1
{
template<class U>
static void f(T(*...p)(U));
};
template<class T>
struct Z2
{
template<class... U>
static void f(T(*...p)(U));
};
Z1<int>::f(f1); // ok
Z2<void>::f(f1); // ok
这说明了一个问题:外层参数包
T
无法扩展,而内层参数包U
仍然是依赖的。
我想当 Y::f
被实例化时,编译器可以将 Y<int, void>
扩展为如下所示:
template<class... U>
void f(int(*p0)(U0), void(*p1)(U1));
其中
U0
和U1
表示参数包的前2个元素U
.
但似乎编译器(g++/clang)拒绝这样做并让整个
p
未被使用。
它在标准中的哪个位置指定了这种行为?它可能是一个标准缺陷还是需要改进的地方?
我已经提取了另一个有效的代码片段,虽然仍然不能满足您的需求,但可能会引导您做一些事情:
void f1(int)
{
}
int f1(char)
{
return 0;
}
template <class Out, class In>
struct UniFunction
{
typedef Out fn(In);
typedef fn* ptr;
};
template<class... T>
struct Y
{
//template<class... U>
//static void f(T(*...p)(U)...);
template <class... U>
struct YY
{
static void f(typename UniFunction<T, U>::ptr...)
{
}
};
};
int main( int argc, char** argv )
{
Y<int>::YY<char>::f(f1);
return 0;
}
这
UniFunction
只是为了清楚起见,也许表明参数包的双重并行扩展并非不可能,只是有一个小问题。
我认为,如果您另外提供一个“模板函数”(带有 typedef 的结构模板),它将从函数指针中提取(使用
type_traits
或其他东西)参数类型和返回类型,您仍然可以强制执行参数类型推导。有了它,您应该能够为 Y::f
函数提供仅一个模板参数(有点像 X...
),从 X
中提取参数类型作为函数指针,然后将其显式传递给 YY
模板,如此处所示。
MSVC 可以很好地处理您的示例。在这种罕见的情况下,我要说 MSVC 是正确的,而 Clang 和 GCC 是错误的。 Clang 和 GCC 不会给出以任何方式帮助暗示标准中存在实际规则的错误消息。
现在,当编译器处理像
Y<int, void>::f(f1, f1)
这样的表达式时,它要做的第一件事就是实例化Y<int, void>
并执行对Y
成员声明的替换。该标准并没有真正说明当来自封闭模板的模板参数包被替换为仍然具有未绑定模板参数包的声明时会发生什么,该模板参数包是同一包扩展的一部分。也就是说,f
的包Y
被指定为T...
后,{int, void}
的声明变成了什么?
template<class... U>
static void f(T(*...p)(U));
// ^ how can you substitute T... = {int, void} into this?
但我认为唯一合理的解释是
T
在U
也被知道之前不能被任何东西取代。所以编译器必须注意f
的声明已经包含“绑定”值:
// this is Y<int, void>'s f
template<class... U>
static void f(T(*...p)(U)) [where T... = {int, void}];
(这显然不是真正的语法)。
U
不在非推导上下文中,因此应该按照通常的规则推导,如果最终与T
的长度不同,则推导失败([temp.deduct.general]/ 11.1).如果推导成功,只有这样包才会扩展,因为 T
和 U
都是已知的。
也许需要修改标准来澄清这一点。但我再次认为这是唯一合理的解释。如果
U
在这里被认为是在非推导上下文中,则需要在标准的非推导上下文列表中明确说明(事实并非如此)。
另一种方法是编译器在实例化
Y<int, void>
的过程中生成类似这样的东西:
template<class... U>
static void f(int (*p)(U@[0]), void (*p)(U@[1]));
其中
U@[i]
是用于索引包 U
的编造语法,并且是非推导上下文。如果认为这就是标准的真正含义,那将是一个真正的延伸。