5

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?

Barry
  • 286,269
  • 29
  • 621
  • 977
prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • Something tells me both lines are invalid, but I can't come up with a reason why yet. – Barry Jan 16 '15 at 02:47
  • @Barry. I hope you're right. Then figuring out the correct two lines in main() will solve the problem without worrying about any fault by the compilers. – prestokeys Jan 16 '15 at 02:50
  • You are getting perfect forwarding wrong: double deduction with one forwarded. And one use is undeducable I think. – Yakk - Adam Nevraumont Jan 16 '15 at 02:50
  • @Yakk. Perhaps, but I put the perfect forwarding just for completeness. When I remove the perfect forwarding, I still get the exact same compiling errors. – prestokeys Jan 16 '15 at 02:52
  • Remove perfect forwarding -- take by `const&` or value. Block deduction on the method pointer (using usual technique). Pass class type explicitly. – Yakk - Adam Nevraumont Jan 16 '15 at 02:59

3 Answers3

6

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);
Windoze
  • 321
  • 2
  • 13
  • Nice easy fix. Thanks a lot. – prestokeys Jan 16 '15 at 03:35
  • I still wonder if perfect forwarding args... is still wrong here (being a partial argument pack). Though I will heed Yakk's advice to not do it. What do others say about that though? Everything seems fine now with perfect forwarding (tested), but I don't know if actually is fine or not. – prestokeys Jan 16 '15 at 03:57
5

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I'm not sure I'm convinced. `Args...` is already in a non-deduced context in `void (T::*f)(Args..., int)`, so why would putting it in another layer of non-deduced context help at all? – T.C. Feb 08 '15 at 04:40
  • I honestly have no idea what is going on here. Note also that there's a special rule for function parameter packs in [temp.deduct.call]/p1: "When a function parameter pack appears in a non-deduced context (14.8.2.5), the type of that parameter pack is never deduced." – T.C. Feb 08 '15 at 06:45
  • @T.C. Well if it's *never* deduced, then at least the first half of my answer is correct - but I should add that sentence. – Barry Feb 08 '15 at 15:37
  • Well, just because some part is non-deduced doesn't make deduction for the entire function call fail, and just because a non-deduced context is involved doesn't mean the function call is invalid. The whole `identity` trick is based on preventing the deduction of `T` from one argument while still allowing it to be deduced from another argument. – T.C. Feb 08 '15 at 16:37
  • @T.C. Should this behaviour be reported as a compiler bug? Sure, there's a ton of ambiguity in the standard's wording on this subject, but I don't see how this could be the intended behaviour. That is, this specific case should work the same way with or without the `identity` wrapper. What do you think? – bogdan Apr 01 '15 at 10:48
2

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

R T::* 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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • A late answer, but this shows a definitive good use for the rarely-used pointers to data members. – prestokeys Feb 08 '15 at 23:34
  • What about the perfect forwarding issue? Let `Derived` have the overloads `void baz (double& d, int n) {std::cout << "baz, double&\n";}` and `void baz (double&& d, int n) {std::cout << "baz, double&&\n";}`. How to take care of that? – prestokeys Feb 08 '15 at 23:51