Что не так с этой программой, вызывающей указатель на функцию с пакетом параметров?

По моему мнению, следующая программа должна, очевидно, напечатать:

1.0 hello world 42

Однако он не компилируется. Почему?

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

Вывод компилятора:

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"));
                                                                           ^

Ответ 1

Во-первых, "hello world" не будет выводить на std::string, он будет выводить на const char*, который не соответствует Callee, поэтому пусть вместо этого ваш вызов будет передан "hello world"s.

Во-вторых, возникает проблема с аргументом типа:

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

Это, по-видимому, в некоторой неопределенности между не выводимым контекстом и выводимым - в том, что он не является не выводимым контекстом (иначе InitialArgTypes... был бы выведен из других параметров), и он не выводим (поскольку он все еще не выполнен). Итак, отпустите еще один шаг и окончательно сделайте это невыводимым контекстом:

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

Теперь InitialArgTypes... будет выводиться из аргументов, переданных в конце. Это то, что мы хотим, поэтому это работает:

CallWithExtraParameter(Callee, 1.0, "hello world"s);

Ответ 2

Почему объяснено в другом ответе.
В любом случае, я хотел бы опубликовать это, чтобы предложить еще одно решение.
Это следует за рабочим примером:

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

Ответ 3

Здесь обобщение на любой хвост размера, который может вам пригодиться. Это также более общий тип вызываемого типа (например, здесь также проверяется указатель на функцию-член).

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