带有部分参数包的Variadic辅助函数

pre*_*eys 5 c++ templates variadic variadic-templates c++11

在以下代码中:

#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
}
Run Code Online (Sandbox Code Playgroud)

我无法通过GCC4.8.1或VS2013来编译上面的两行.他们只会编译一个而不会编译另一个(并且他们不同意哪一行是正确的也不正确).两个编译器都错误消息指出模板扣除失败.那究竟是什么错?我已将所有模板参数放在最后一行(我认为可以推断),但它仍然无法由GCC推断,尽管VS可以.然而,b.foo(&Base::bar);当我为此放置模板参数时,VS无法推断出该行的模板参数,但GCC可以在没有任何模板参数的情况下推导出它们.这里完全不知所措.两个编译器都在这里窃听?程序员的任何修复可能吗?

Win*_*oze 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);
}
Run Code Online (Sandbox Code Playgroud)

如果你必须放在int参数列表的末尾,你可以使用identity技巧,如@Barry所说.

准系统identity实现可以很简单:

template<typename T>
struct identity {
    typedef T type;
};
Run Code Online (Sandbox Code Playgroud)

然后您可以手动推导参数类型:

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);
Run Code Online (Sandbox Code Playgroud)


Bar*_*rry 5

我认为这两个调用都是无效的,因为它们都涉及非推断的上下文.从§14.8.2.5开始:

未推断的上下文是:

- [..]

- 函数参数包,它不会出现在参数声明列表的末尾.

如果以包含非推导上下文的方式指定类型名称,则包含该类型名称的所有类型也是非推断的.

当你有void (T::*f)(Args..., int),那是在非推导的上下文中,因为函数内部的函数参数包不会在最后发生.指向成员的参数列表是非推导的这一事实使得整个函数调用是非推导的.因此无法推断出此调用:

b.helper(&Base::bar);
Run Code Online (Sandbox Code Playgroud)

对于第二个,尽管它看起来虽然你是明确指定的Args...,但是参数void (T::*f)(Args..., int)仍然在非推导的上下文中,因此编译器无法知道是否需要更多 Args.

因此,一种解决方案是通过使用,例如,向后使用身份技巧来迫使该论证不必推断:

template <typename T, typename... Args> 
void foo (void (T::*)(typename identity<Args>::type..., int), Args...);
Run Code Online (Sandbox Code Playgroud)

这样,这两行都编译:

b.helper(&Base::bar);
d.helper<Derived, double>(&Derived::baz, 3.14);
Run Code Online (Sandbox Code Playgroud)

虽然现在你必须确保Args...如果你没有明确指定它就完全正确.


T.C*_*.C. 2

我根本不会将第一个参数写为指向成员函数的指针。

在您的特定情况下,它需要将第一个放入Args...非推导的上下文中 - 并且标准对于之后应该发生的事情非常清楚,特别是考虑到 [temp.deduct.call]/p1 中的规则

当函数参数包出现在非推导上下文 (14.8.2.5) 中时,永远不会推导该参数包的类型。

我不知道当你写的时候这条规则的含义是什么void (T::*)(typename identity<Args>::type..., int)。编译器之间也存在分歧。

即使在正常情况下,您也必须编写大约 12 个重载来匹配所有可能形式的成员函数指针(4 个可能的cv-qualifier-seq乘以 3 个可能的ref-qualifiers)。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);
    // ...
}
Run Code Online (Sandbox Code Playgroud)

R T::*匹配所有指向成员的指针;当您将指针传递给成员函数时,R会被推导为函数类型。如果您想强制执行 R-must-be-a-function,您可以static_assertstd::is_function<R>::value.

演示