在以下代码中:
#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可以在没有任何模板参数的情况下推导出它们。这里完全不知所措。两个编译器都在这里窃听?程序员可以修复任何问题吗?
答案 0 :(得分:6)
参数包必须放在参数列表的末尾才能自动推断。
编译器无法从给定的参数列表中推导出(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
放在参数列表的末尾,则可以使用identity
技巧,如@Barry所说。
准系统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);
答案 1 :(得分:5)
我认为这两个调用都是无效的,因为它们都涉及非推断的上下文。从§14.8.2.5开始:
未推断的上下文是:
- [..]
- 不在 parameter-declaration-list 末尾出现的函数参数包。
如果以包含非推断上下文的方式指定类型名称,则包含该类型名称的所有类型也不会被推断。
当你有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...
。
答案 2 :(得分:2)
我根本不会将第一个参数写为成员函数的指针。
在你的特定情况下,它需要将第一个Args...
放入一个非推导的上下文中 - 并且标准很明显是关于之后应该发生的事情的泥泞,特别是考虑到[temp.deduct。中的规则]。打电话] / p1
当函数参数包出现在非推导的上下文中时 (14.8.2.5),从不推断出该参数包的类型。
当您编写void (T::*)(typename identity<Args>::type..., int)
时,我不知道此规则的含义是什么。编制者也不同意。
即使在正常情况下,您也必须编写12个重载以匹配所有可能形式的成员函数指针(4个可能 cv-qualifier-seq s次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-must-a-function,您可以在static_assert
上std::is_function<R>::value
。