11

According to my understanding, the following program should obviously print:

1.0 hello world 42

However, it fails to compile. Why?

#include <iostream>
#include <string>
using namespace std;

template<class... InitialArgTypes>
void CallWithExtraParameter(void (*funcPtr)(InitialArgTypes..., int), InitialArgTypes... initialArgs)
{
    (*funcPtr)(initialArgs..., 42);
}

void Callee(double a, string b, int c)
{
    cout << a << " " << b << " " << c << endl;
}

int main()
{
    CallWithExtraParameter<double, string>(Callee, 1.0, string("hello world"));
}

Compiler output:

prog.cpp: In function 'int main()':
prog.cpp:18:75: error: no matching function for call to 'CallWithExtraParameter(void (&)(double, std::string, int), double, std::string)'
  CallWithExtraParameter<double, string>(Callee, 1.0, string("hello world"));
                                                                           ^
prog.cpp:6:6: note: candidate: template<class ... InitialArgTypes> void CallWithExtraParameter(void (*)(InitialArgTypes ..., int), InitialArgTypes ...)
 void CallWithExtraParameter(void (*funcPtr)(InitialArgTypes..., int), InitialArgTypes... initialArgs)
      ^
prog.cpp:6:6: note:   template argument deduction/substitution failed:
prog.cpp:18:75: note:   mismatched types 'int' and 'double'
  CallWithExtraParameter<double, string>(Callee, 1.0, string("hello world"));
                                                                           ^
user253751
  • 57,427
  • 7
  • 48
  • 90

3 Answers3

5

First, "hello world" wouldn't deduce to std::string, it would deduce to const char*, which doesn't match Callee, so let's fix your call to be pass "hello world"s instead.

Second, there appears to be some issue with having an argument of type:

void (*funcPtr)(InitialArgTypes..., int)

That is apparently in some limbo between non-deduced context and deducible - in that it's not a non-deduced context (otherwise InitialArgTypes... would've been deduced from the other parameters) and it's not deducible (because it still failed). So let's go one step further and definitively make it a non-deduced context:

template <class T> struct identity { using type = T; };
template <class T> using identity_t = typename identity<T>::type;

template <class... InitialArgTypes>
void CallWithExtraParameter(void (*funcPtr)(identity_t<InitialArgTypes>..., int),
        InitialArgTypes... initialArgs)
{
    (*funcPtr)(initialArgs..., 42);
}

Now, InitialArgTypes... will be deduced from the arguments passed in at the end. Which is what we want, so this works:

CallWithExtraParameter(Callee, 1.0, "hello world"s);
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 4
    Isn't the string deduced as `const char [N]`? – skypjack Mar 22 '16 at 23:25
  • 2
    Didn't I explicitly specify the template parameters as ``? – user253751 Mar 22 '16 at 23:29
  • @skypjack No, the args are deduced by value, not by reference. – Barry Mar 22 '16 at 23:30
  • 1
    @Barry Well, GCC was saying that `std::string` and `const char[12]` where not the same type in the first example posted by the OP, so... – skypjack Mar 22 '16 at 23:32
  • @T.C. The resolution there says that it will never be deduced. But it even fails when OP explicitly provides the types (unlike the example given with `g`) – Barry Mar 22 '16 at 23:49
  • Ugh. Yeah, I've seen this exact scenario multiple times on SO, and I can never make any sense out of the standard text :/ – T.C. Mar 22 '16 at 23:54
  • @T.C. We'll if you cant, what hope have I? :) I'm sticking with my purgatory theory. I feel like it just generally sums up templates. – Barry Mar 22 '16 at 23:56
  • Making things extra fun: `template void CallWithExtraParameter(void (*funcPtr)(T..., int, T...), T... t);` (with suitable changes elsewhere) works with Clang with or without explicitly specified template arguments, but not GCC. – T.C. Mar 23 '16 at 00:02
  • ...and then Clang does something *really* weird: http://coliru.stacked-crooked.com/a/fc60c55cb4da9140 I'm going to go with buggy compilers all around plus clear-as-mud standardese. A wonderful combination, to be sure. – T.C. Mar 23 '16 at 00:10
  • @T.C. At least that OK (in a vacuum) I understand. But why that line is OK and the others error? Sounds like a new CWG issue... – Barry Mar 23 '16 at 00:22
  • Sorry, but I can't accept this answer because the template parameters are explicitly specified and shouldn't need to be deduced. – user253751 Feb 22 '23 at 19:27
2

The why has been explained in another answer.
Anyway, I'd like to post that one to suggest one more solution.
It follows a working example:

#include <iostream>
#include <string>

using namespace std;

template<class... C>
struct Fn {
    using type = void (*)(C..., int);
};

template<class... InitialArgTypes>
void CallWithExtraParameter(typename Fn<InitialArgTypes...>::type funcPtr, InitialArgTypes... initialArgs)
{
    (*funcPtr)(initialArgs..., 42);
}

void Callee(double a, string b, int c)
{
    cout << a << " " << b << " " << c << endl;
}

int main()
{
    CallWithExtraParameter<double, string>(Callee, 1.0, string("hello world"));
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Not sure why somebody downvoted this; this actually isn't bad. – T.C. Mar 22 '16 at 23:55
  • @T.C. Actually I don't know, it's quite annoying to see that people downvotes valid responses without a comment. Anyway, your *isn't bad* is more than an upvote for me!! Thank you. – skypjack Mar 23 '16 at 06:37
0

Here's a generalization to any size tail that might be of use to you. It is also more generic regarding the callable type (e.g. member function pointer is tested here too).

#include <iostream>
#include <tuple>
#include <utility>
#include <string>

template <typename Callable> struct Invoke;

template <typename R, typename... Args>
struct Invoke<R(*)(Args...)> {
    template <typename F, typename Tuple, std::size_t... Is, typename... As>
    static R execute (F funcPtr, Tuple&& tuple, std::index_sequence<Is...>, As&&... as) {
        return (*funcPtr)(std::forward<As>(as)..., std::get<Is>(std::forward<Tuple>(tuple))...); 
    }
};

template <typename R, typename C, typename... Args>
struct Invoke<R(C::*)(Args...)> {
    template <typename F, typename Tuple, std::size_t... Is, typename... As>
    static R execute (F f, Tuple&& tuple, std::index_sequence<Is...>, C& c, As&&... as) {
        return (c.*f)(std::forward<As>(as)..., std::get<Is>(std::forward<Tuple>(tuple))...); 
    }
    template <typename F, typename Tuple, std::size_t... Is, typename... As>
    static R execute (F f, Tuple&& tuple, std::index_sequence<Is...>, C* c, As&&... as) {
        return (c->*f)(std::forward<As>(as)..., std::get<Is>(std::forward<Tuple>(tuple))...); 
    }
};

template <typename R, typename C, typename... Args>
struct Invoke<R(C::*)(Args...) const> {
    template <typename F, typename Tuple, std::size_t... Is, typename... As>
    static R execute (F f, Tuple&& tuple, std::index_sequence<Is...>, C& c, As&&... as) {
        return (c.*f)(std::forward<As>(as)..., std::get<Is>(std::forward<Tuple>(tuple))...); 
    }
    template <typename F, typename Tuple, std::size_t... Is, typename... As>
    static R execute (F f, Tuple&& tuple, std::index_sequence<Is...>, const C* c, As&&... as) {
        return (c->*f)(std::forward<As>(as)..., std::get<Is>(std::forward<Tuple>(tuple))...); 
    }
};

template <typename Functor>
struct Invoke : Invoke<decltype(&Functor::operator())> {};
// etc...

template <typename R = void, typename F, typename Tuple, typename... Args>
R invokeWithTupleTail (F funcPtr, Tuple&& tuple, Args&&... args) {
    return Invoke<F>::execute(funcPtr, std::forward<Tuple>(tuple),
        std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}, std::forward<Args>(args)...);
}

// Testing
struct Thing {
    int call (char k, int n, double a, std::string b, int c) {
        std::cout << k << ' ' << n << ' ' << a << ' ' << b << ' ' << c << '\n';
        return 5;
    }
    int doIt (char k, int n, double a, std::string b, int c) const {
        std::cout << k << ' ' << n << ' ' << a << ' ' << b << ' ' << c << '\n';
        return 12;
    }
    int operator() (char k, int n, double a, std::string b, int c) const {
        std::cout << k << ' ' << n << ' ' << a << ' ' << b << ' ' << c << '\n';
        return 20;
    }
};

void foo (char k, int n, double a, std::string b, int c) {
    std::cout << k << ' ' << n << ' ' << a << ' ' << b << ' ' << c << '\n';
}

int bar (char k, int n, double a, std::string b, int c) {
    std::cout << k << ' ' << n << ' ' << a << ' ' << b << ' ' << c << '\n';
    return 10;
}

int main() {
    const auto tupleTail = std::make_tuple(1.5, std::string("hello"), 42);
    invokeWithTupleTail(foo, tupleTail, 'a', 8);  // a 8 1.5 hello world 42
    int a = invokeWithTupleTail<int>(&bar, tupleTail, 'a', 8);  // a 8 1.5 hello world 42
    std::cout << a << '\n';  // 10

    Thing thing;
    a = invokeWithTupleTail<int>(&Thing::call, tupleTail, thing, 'a', 8);  // a 8 1.5 hello world 42
    std::cout << a << '\n';  // 5
    a = invokeWithTupleTail<int>(&Thing::doIt, tupleTail, &thing, 'a', 8);  // a 8 1.5 hello world 42
    std::cout << a << '\n';  // 12
    a = invokeWithTupleTail<int>(&Thing::operator(), tupleTail, thing, 'a', 8);  // a 8 1.5 hello world 42
    std::cout << a << '\n';  // 20
}
prestokeys
  • 4,817
  • 3
  • 20
  • 43