2015-01-16 62 views
5

在以下代码:与局部参数包可变参数的辅助函数

#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

东西告诉我,两条线都是无效的,但我不能拿出一个理由为什么呢。 – Barry

+0

@Barry。我希望你是对的。然后找出main()中正确的两行就可以解决问题,而不用担心编译器会有任何问题。 – prestokeys

+0

你正在得到完美的转发错误:一个转发双重扣除。我认为一种用途是不可诱导的。 – Yakk

回答

2

我不会写的第一个参数作为成员函数指针在所有。

在你的特殊情况下,它需要把第一个Args...放到一个非推导的上下文中 - 并且标准清楚地表明事后会发生什么,尤其是考虑到[temp.deduct.call]/p1

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

我不知道这个规则的含义是什么,当你写void (T::*)(typename identity<Args>::type..., int)而不是。编译器也不同意。

即使在正常情况下,你必须写一些12过载来匹配成员函数指针(4可能CV限定符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功能,你可以在std::is_function<R>::value上使用static_assert

Demo

+0

答案很晚,但这对于数据成员很少使用的指针显示出明确的良好用途。 – prestokeys

+0

完美的转发问题呢?让'Derived'有重载'void baz(double&d,int n){std :: cout <<“baz,double &\n";}'和'void baz(double && d,int n){std :: cout <<” baz,双&&\n";}'。如何照顾呢? – prestokeys

5

我认为这两个调用都是无效的,因为两者都涉及一个非推导的上下文。从§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...完全正确,如果你没有明确指定它。

+0

Paging @Columbo :) – Barry

+0

我不确定我是否确信。 'Args ...'已经在'void(T :: * f)(Args ...,int)'的非推导语境中,那么为什么要把它放在另一个非推导的上下文层中? –

+0

@ T.C。嗯。我在这里完全错了吗? – Barry

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); 
+0

不错的简单修复。非常感谢。 – prestokeys

+0

我仍然怀疑是否完美转发参数...在这里仍然是错误的(作为一个部分参数包)。虽然我会听取Yakk的建议,不这样做。别人对此有何评论?一切看起来都很好,而且完美的转发(测试),但我不知道实际上是否好。 – prestokeys