Variadic helper function with partial argument package

In the following code:

#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 } 

I cannot get GCC4.8.1 or VS2013 to compile both lines above. They will compile only one, but not the other (and they do not agree on which line is correct and incorrect). The error message indicates that the template deduction was not performed by both compilers. So what is wrong at all? I put all the template parameters on the last line (which I thought would be output), but GCC still could not be output, although VS could. However, VS cannot print the template arguments for the string b.foo(&Base::bar); when I put template arguments for this, but GCC can output them without template arguments. Here are completely confused. Are both compilers bugged here? Is any correction possible on the part of the programmer?

+5
source share
3 answers

I would not write the first argument as a pointer to a member function in general.

In your particular case, it was necessary to put the first Args... in an undetectable context - and the standard becomes clear as dirt about what should happen after that, especially considering the rule in [temp.deduct.call] / p1, which

When a package of function parameters appears in an undetectable context (14.8.2.5), the type of this parameter package is never displayed.

I do not know what the consequences of this rule are when you write void (T::*)(typename identity<Args>::type..., int) . Compilers also disagree with each other.

Even in the normal case, you will need to write 12 overloads to match all possible forms of pointers to member functions (4 possible cv-qualifier-seqs times 3 possible ref-qualifiers). In your case, it is probably safe to skip some (e.g. volatile and && ), but this is still annoying code duplication. Also, if you use Args... twice in the output contexts, they are output independently, and the output types must match exactly, which can become messy for the end user. ( std::max(1, 2.5) , anyone?)

Instead, I simply write it as a member pointer:

 template <typename T, typename... Args, typename R> void Base::helper (RT::*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); // ... } 

RT::* matches all member pointers; when you pass a pointer to a member function, R is inferred as the type of the function. If you want to apply the R-binding function, you can static_assert on std::is_function<R>::value .

Demo

+2
source

The parameter package must be placed at the end of the parameter list in order to be automatically displayed.

The compiler cannot deduce (Args..., int) from the parameter list, use (int, Args...) instead, and the program compiles.

 #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); } 

If you need to put an int at the end of the parameter list, you can use the identity trick, as @Barry said.

Implementing barebone identity can be simple:

 template<typename T> struct identity { typedef T type; }; 

Then you can manually infer parameter types:

 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); 
+6
source

I think both calls are invalid because both are related to unallocated context. From ยง14.8.2.5:

Impossible contexts:

- [..]

- a package of function parameters that does not occur at the end of the list of parameter declarations.

If a type name is specified in a way that includes an inferior context, all types that contain that type name are also not inferred.

If you have void (T::*f)(Args..., int) , that is, in an undetectable context, because the package of function parameters inside the function does not occur at the end. The fact that the member-pointer argument list is not displayed makes the entire function call not output. Thus, this call cannot be deduced:

 b.helper(&Base::bar); 

For the second one, although it looks, although you explicitly specify Args... , the argument void (T::*f)(Args..., int) is still in an undetectable context, so the compiler does not know if more Args is required.

One solution is to make this argument not output using, say, an identity trick back:

 template <typename T, typename... Args> void foo (void (T::*)(typename identity<Args>::type..., int), Args...); 

Thus, both of these lines are compiled:

 b.helper(&Base::bar); d.helper<Derived, double>(&Derived::baz, 3.14); 

Although now you need to make sure that you get Args... exactly if you have not explicitly specified it.

+5
source

Source: https://habr.com/ru/post/1211215/


All Articles