带有部分参数包的可变参数辅助函数
Variadic helper function with partial argument pack
在下面的代码中:
#include <iostream>
struct Base {
virtual ~Base() = default;
template <typename T, typename... Args> void helper (void (T::*)(Args..., int), Args...);
void bar (int n) {std::cout << "bar " << n << std::endl;}
};
struct Derived : Base {
void baz (double d, int n) {std::cout << "baz " << d << ' ' << n << std::endl;}
};
template <typename T, typename... Args>
void Base::helper (void (T::*f)(Args..., int), Args... args) {
// A bunch on lines here (hence the motivation for the helper function)
for (int n = 0; n < 5; n++)
(dynamic_cast<T*>(this)->*f)(args..., n);
// ...
}
int main() {
Base b;
Derived d;
b.helper(&Base::bar); // GCC 4.8.1 will accept this, Visual Studio 2013 won't.
d.helper<Derived, double>(&Derived::baz, 3.14); // Visual Studio 2013 will accept this, GCC 4.8.1 won't
}
我无法让GCC4.8.1或VS2013编译上面的两行。 他们只会编译一个,而不是另一个(他们也不同意哪一行是正确的,哪一行不正确)。 错误消息指出两个编译器的模板推导失败。 那么到底出了什么问题呢? 我已经将所有模板参数放在最后一行(我认为这是可以推导的),但它仍然无法通过 GCC 推断,尽管 VS 可以。 然而,当我放置模板参数时,VS 无法推断出b.foo(&Base::bar);
行的模板参数,但 GCC 可以在没有任何模板参数的情况下推断它们。 在这里完全困惑。 两个编译器都在这里出错了吗? 程序员方面有什么可能的修复吗?
参数包必须放在参数列表的末尾才能自动推导。
编译器无法从给定的参数列表中推断出(Args..., int)
,请改用(int, Args...)
,程序将编译。
#include <iostream>
struct Base {
virtual ~Base() = default;
template <typename T, typename... Args> void helper (void (T::*)(int, Args...), Args...);
void bar (int n) {std::cout << "bar " << n << std::endl;}
};
struct Derived : Base {
void baz (int n, double d) {std::cout << "baz " << d << ' ' << n << std::endl;}
};
template <typename T, typename... Args>
void Base::helper (void (T::*f)(int, Args...), Args... args) {
// A bunch on lines here (hence the motivation for the helper function)
for (int n = 0; n < 5; n++)
(dynamic_cast<T*>(this)->*f)(n, args...);
// ...
}
int main() {
Base b;
Derived d;
b.helper(&Base::bar);
d.helper<Derived, double>(&Derived::baz, 3.14);
}
如果必须int
放在参数列表的末尾,则可以按照@Barry所说的使用identity
技巧。
准系统identity
实施可以像以下那样简单:
template<typename T>
struct identity {
typedef T type;
};
然后您可以手动推断参数类型:
template <typename T, typename... Args>
void Base::helper (void (T::*f)(typename identity<Args>::type..., int), typename identity<Args>::type... args) {
// A bunch on lines here (hence the motivation for the helper function)
for (int n = 0; n < 5; n++)
(dynamic_cast<T*>(this)->*f)(args..., n);
// ...
}
b.helper<Base>(&Base::bar);
d.helper<Derived, double>(&Derived::baz, 3.14);
我认为这两个调用都是无效的,因为两者都涉及非推导上下文。从 §14.8.2.5 开始:
非推导上下文是:
— [ . ]
— 不出现在参数声明列表末尾的函数参数包。
当以包含非推导上下文的方式指定类型名称时,构成该类型名称的所有类型也是非推导的。
当你有 void (T::*f)(Args..., int)
时,那是在非推导上下文中,因为函数内的函数参数包不会在末尾出现。指向成员的指针的参数列表是非推导的,这一事实使整个函数调用不可推导。因此,不能推断出这个调用:
b.helper(&Base::bar);
对于第二个,即使看起来你显式指定了Args...
,参数void (T::*f)(Args..., int)
仍然处于非推导上下文中,因此编译器无法知道是否需要更多Args
。
因此,一种解决方案是迫使该论点不必被推导,例如,通过使用反向恒等技巧:
template <typename T, typename... Args>
void foo (void (T::*)(typename identity<Args>::type..., int), Args...);
这样,这两行都可以编译:
b.helper(&Base::bar);
d.helper<Derived, double>(&Derived::baz, 3.14);
尽管现在您必须确保如果不明确指定Args...
正确。
我根本不会将第一个参数编写为指向成员函数的指针。
在你的特定情况下,它需要将第一个Args...
放在一个非推导的上下文中 - 并且标准对于之后应该发生的事情是明确的,特别是考虑到 [temp.deduct.call]/p1 中的规则
当函数参数包出现在非推导上下文中时 (14.8.2.5),永远不会推断出该参数包的类型。
我不知道当你写void (T::*)(typename identity<Args>::type..., int)
时这个规则的含义是什么。编译器也彼此不同意。
即使在正常情况下,您也必须编写大约 12 个重载来匹配所有可能形式的成员函数指针(4 个可能的 cv-qualifier-seqs 乘以 3 个可能的 ref 限定符)。在您的情况下,跳过一些(例如volatile
和&&
)可能是安全的,但它仍然是烦人的代码重复。此外,如果在推导上下文中使用两次Args...
,它们是独立推导的,推导的类型必须完全匹配,这可能会让最终用户感到混乱。(std::max(1, 2.5)
,有人吗?
相反,我只是将其写为指向成员的指针:
template <typename T, typename... Args, typename R>
void Base::helper (R T::*f, Args... args) {
// A bunch of lines here (hence the motivation for the helper function)
for (int n = 0; n < 5; n++)
(dynamic_cast<T*>(this)->*f)(args..., n);
// ...
}
R T::*
匹配指向成员的所有指针;当您传递指向成员函数的指针时,R
被推导出为函数类型。如果要强制执行 R 必须是一个函数,可以在 std::is_function<R>::value
上static_assert
。
演示。
- 在不传递参数数量且只有3个点的情况下,如何使用变差函数
- 如何使用可变参数模板强制转换每个变体类型
- 关于如何在具有单个参数的变体构造中选择替代方案?
- 调用参数排列不变函数 f(i++, i++)
- 参数归纳与标准::变体
- 模板化回调参数的逆变,如 C# 中的逆变
- 如何在没有参数包的情况下编写变差函数
- 通过具有嵌套类的工厂类获取多个变异类模板参数包
- 获取模板参数的成员变量值列表
- 保留短 lambda 用作函数的中间参数,使用 clang 格式保持不变
- 如何定义变体<x,y,z>提取模板参数的子类型
- 正确对齐内存模板,参数顺序不变
- 递归中不同参数类型的变元模板函数
- 通过函数指针传递给变差函数的参数会更改其值
- 提升预定义为带有参数的全局 lambda 的变体访问者
- 使用可变参数模板参数提升变体访问者
- boost ::变体 - 为什么模板参数比const字符串参数具有更高的优先级
- 将变参数包中的值加载到临时数组中
- 使用额外参数提升变体访客
- 正在将动态数组元素解析为参数?(变音符)