【问题标题】:What is wrong with this program calling a function pointer with a parameter pack?这个程序用参数包调用函数指针有什么问题?
【发布时间】:2016-07-10 01:54:34
【问题描述】:

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

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

【问题讨论】:

  • 你试过用 double 代替 int 吗?
  • 至少应该是CallWithExtraParameter&lt;double, string&gt;(Callee, 1.0, string{"hello world"});。不应该吗?
  • @skypjack 可能。 (我的实际代码没有在这里传递字符串,我只是为 MCVE 选择了一些随机类型)
  • 奇怪的是,分离参数推导仍然失败:ideone.com/50KlFg

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


【解决方案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);

【讨论】:

  • 字符串不是推导出为const char [N]吗?
  • 我不是明确指定模板参数为&lt;double, string&gt;吗?
  • @skypjack 不,参数是按值推导的,而不是通过引用推导的。
  • @Barry 好吧,GCC 说 std::stringconst char[12] 在 OP 发布的第一个示例中的类型不同,所以...
  • @T.C.那里的决议说它永远不会被推断出来。但是当 OP 明确提供类型时它甚至会失败(与g 给出的示例不同)
【解决方案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"));
}

【讨论】:

  • 不知道为什么有人反对这个;这其实还不错。
  • @T.C.实际上我不知道,看到人们在没有评论的情况下对有效的回复投反对票是很烦人的。不管怎样,你的还不错对我来说不仅仅是一个赞成票!!谢谢。
【解决方案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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-11
    • 2017-01-09
    • 2013-05-01
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    相关资源
    最近更新 更多