简体   繁体   中英

Variadic helper function with partial argument pack

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 can't get either GCC4.8.1 or VS2013 to compile both lines above. They will compile only one but not the other (and they don't agree on which line is correct and incorrect either). The error message states template deduction failed by both compilers. So what's actually wrong? I've put all the template parameters in the last line (which I thought would be deducible), but it still cannot be deduced by GCC, though VS can. Yet VS cannot deduce the template arguments for the b.foo(&Base::bar); line when I place the template arguments for that, yet GCC can deduce them without any template arguments. Totally bewildered here. Are both compilers bugged here? Any fix possible on the programmer's part?

Parameter pack must be placed at the end of parameter list in order to be deduced automatically.

Compiler cannot deduce (Args..., int) from given parameter list, use (int, Args...) instead and the program will compile.

#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 have to put int at the end of parameter list, you can use identity trick as @Barry said.

A barebone identity implementation can be as simple as:

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

Then you can manually deduce 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);

I think both calls are invalid because both involve a non-deduced context. From §14.8.2.5:

The non-deduced contexts are:

— [ .. ]

— A function parameter pack that does not occur at the end of the parameter-declaration-list .

When a type name is specified in a way that includes a non-deduced context, all of the types that comprise that type name are also non-deduced.

When you have void (T::*f)(Args..., int) , that is in a non-deduced context because the function parameter pack inside the function does not occur at the end. The fact that the pointer-to-member's argument list is non-deduced makes the entire function call non-deduced. Thus this call cannot be deduced:

b.helper(&Base::bar);

For the second one, even though it looks though you are explicitly specifying Args... , the argument void (T::*f)(Args..., int) is still in a non-deduced context, so the compiler has no way of knowing if more Args are necessary.

One solution is thus to force that argument to not have to be deduced, by using, say, the identity trick backwards:

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

That way, both of these lines compile:

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

Although now you have to make sure that you get Args... exactly right if you don't explicitly specify it.

I wouldn't write the first argument as a pointer to member function at all.

In your particular case, it required putting the first Args... into a non-deduced context - and the standard is clear as mud about what is supposed to happen afterwards, especially given the rule in [temp.deduct.call]/p1 that

When a function parameter pack appears in a non-deduced context (14.8.2.5), the type of that parameter pack is never deduced.

I have no idea what the implications of this rule are when you write void (T::*)(typename identity<Args>::type..., int) instead. The compilers disagree with each other, too.

Even in the normal case, you'll have to write some 12 overloads to match all possible forms of member function pointers (4 possible cv-qualifier-seq s times 3 possible ref-qualifiers ). In your case it's probably safe to skip some (such as volatile and && ), but it's still annoying code duplication. In addition, if you use Args... twice in deduced contexts, they are deduced independently and the deduced types must match exactly, which can get messy for the end user. ( std::max(1, 2.5) , anyone?)

Instead, I would just write it as a pointer-to-member:

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);
    // ...
}

RT::* matches all pointers to members; when you pass a pointer to member function, R gets deduced to be a function type. If you want to enforce R-must-be-a-function, you can static_assert on std::is_function<R>::value .

Demo .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM