这个程序用参数包调用函数指针有什么问题?

时间:2016-03-22 23:08:30

标签: c++ templates c++11 variadic-templates

根据我的理解,下面的程序显然应该打印出来:

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

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

3 个答案:

答案 0 :(得分:5)

首先,"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);

答案 1 :(得分:1)

为什么已在另一个答案中解释 无论如何,我想发布一个建议再提出一个解决方案 它遵循一个工作示例:

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

答案 2 :(得分:0)

这是对你可能有用的任何大小尾巴的概括。关于可调用类型,它也更通用(例如,此处也测试了成员函数指针)。

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