从一个变量模板类中推导出类型包,并声明一个相同类型包的参数。

问题描述 投票:3回答:2

首先,对不起,问题标题不清楚,如果你觉得有更好的说明方式,欢迎编辑。

我有一个班级。

template <typename ...Arguments>
class CSignal
{
    template <typename ...ActualArguments>
    void invoke(ActualArguments&&... args) const {}
};

还有一个,这是我的一个问题。

class SomeClass
{
    template<typename ...Arguments>
    void invokeQueued(CSignal<Arguments...>& signal, const Arguments&... args)
    {
        m_queue.emplace_back([=](){signal.invoke(args...);});
    }

    std::deque<std::function<void (void)>> m_queue;
};

问题:

CSignal<float> signal;
int i = 0;
SomeClass().invokeQueued(signal, i);

错误:

template parameter 'Arguments' is ambiguous
could be 'float'
or       'int'

可能的天真解决方案

template<typename ...FormalArguments, typename ...ActualArguments>
void invokeQueued(CSignal<FormalArguments...>& signal, const ActualArguments&... args)
{
    m_queue.emplace_back([=](){signal.invoke(args...);});
}

在这一特定情况下是不可接受的,因为我。需要 来捕捉参数的值(将它们复制到lambda中),并从 ActualArgumentsFormalArguments 必须 时有 invokeQueued 的时候,而不是在调用lambda的时候。

如果我可以 typedef 中的参数包。CSignal 类,我会做。

template<typename ...FormalArguments>
void invokeQueued(CSignal<FormalArguments...>& signal, const CSignal<FormalArguments...>::argument_types&... args)
{
    m_queue.emplace_back([=](){signal.invoke(args...);});
}

但这似乎不可能 有什么解决办法吗?

c++ c++11 templates variadic-templates
2个回答
8
投票

你遇到的错误是由于参数的类型不同而引起的,而编译器对每个这样的参数对只有一个类型模板参数:从 CSignal的签名,它看到 float而从第二个论点的推导类型中可以看到 int的一个元素进行匹配,并且两者都必须与 Arguments 包。这就是歧义的来源。

为了解决这个问题,你可以通过引入一个叫做 非演绎语境,比如下面的 身份 技巧。

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

class SomeClass
{
public:
    template <typename... Arguments>
    void invokeQueued(CSignal<Arguments...>& signal,
                      const identity_t<Arguments>&... args)
    //                      ~~~~~~~~~^
    {
        m_queue.emplace_back([=](){signal.invoke(args...);});
    }

    std::deque<std::function<void(void)>> m_queue;
};

编译器不会尝试推导任何模板参数,这些参数属于 嵌套名称说明者 语法,这基本上就是 identity 它引入了 identity<T>::type 句法,这样 T 是留给作用域解析运算符的,但它仍然可以完整地用于函数声明中。

DEMO


另外,你也可以在lambda表达式(C++14)中捕获参数时,将其转换为适当类型的衰减副本存储起来。

#include <utility>
#include <type_traits>
#include <cstddef>

class SomeClass
{
public:
    template <typename... FormalArguments, typename... ActualArguments>
    void invokeQueued(CSignal<FormalArguments...>& signal, ActualArguments&&... args)
    {
        invokeQueued(signal, std::index_sequence_for<ActualArguments...>{}, std::forward<ActualArguments>(args)...);
    }

    template <typename... FormalArguments, typename... ActualArguments, std::size_t... Is>
    void invokeQueued(CSignal<FormalArguments...>& signal, std::index_sequence<Is...>, ActualArguments&&... args)
    {
        m_queue.emplace_back(
          [signal, t = std::tuple<std::decay_t<FormalArguments>...>(std::forward<ActualArguments>(args)...)]
          (){signal.invoke(std::get<Is>(t)...);});
    }

    std::deque<std::function<void(void)>> m_queue;
};

DEMO 2


1
投票

使用身份诀窍来创建一个 非演绎语境 对于 Arguments&&....

template <typename T>
class Identity
{
public:
using type = T;
};

template<typename ...Arguments>
void invokeQueued(CSignal<Arguments...>& signal, const typename Identity<Arguments>::type &... args)
{
    m_queue.emplace_back([=](){signal.invoke(args...);});
}
© www.soinside.com 2019 - 2024. All rights reserved.