【问题标题】:Getting appropriate storage value type?获取适当的存储值类型?
【发布时间】:2012-12-31 20:02:00
【问题描述】:

假设我们有一个 Box 类如下:

struct Base {}

template<typename T>
struct Box : Base
{
    template<typename... Args>
    Box(Args&&... args)
        : t(forward<Args>(args)...)
    {}

    T t;
}

然后我们有一个函数 MakeBox:

template<typename X>
Base* MakeBox(X&& x)
{
    return new Box<???>(forward<X>(x));
}

X 类型是从调用 MakeBox 时使用的参数推导出来的。

然后我们需要从 X 以某种方式计算出适当的“存储类型”参数 T。

我想如果我们只是天真地使用:

    return new Box<X>(forward<X>(x));

那么这将导致问题。

明明std::bindstd::function需要处理这些问题,他们是怎么做的?

std::decay 在这里有用吗?

【问题讨论】:

  • std::remove_reference&lt;X&gt; 不是您要找的吗?
  • @jogojapan:这就是std::bind用来存储绑定参数的东西吗?
  • std::decay 正是您想要的。
  • 所以是return new Box&lt;decay&lt;X&gt;::type&gt;(forward&lt;X&gt;(x))std::decay 文档说“这是按值传递时应用于所有函数参数的类型转换。”。我不确定我完全理解这意味着什么。
  • 问题是关于bind 是如何工作的以及它使用了哪些辅助函数(这显然取决于实现),还是关于如何处理代码中的情况?

标签: c++ c++11


【解决方案1】:

如果我正确理解您想要实现的目标,那么您需要使用std::decay。假设您向MakeBox() 提供S 类型的对象,则通用引用X&amp;&amp; 将被解析为使函数参数为S&amp;S&amp;&amp; 类型,具体取决于您的参数是否为(分别)左值或右值。

为了实现这一点并且由于通用引用的 C++11 规则,在第一种情况下,模板参数将被推导出为 X=S&amp;(这里 X 不能作为 Box&lt;&gt; 的参数,因为您的成员变量必须是对象而不是对象引用),而在第二种情况下,它将被推断为X=S(这里X 可以作为Box&lt;&gt; 的参数)。通过应用std::decay,您还将在将X作为模板参数提供给Box&lt;&gt;之前隐式地将std::remove_reference应用到推导类型Box&lt;&gt;,您将确保X始终等于S和从不S&amp;(请记住,X从不在此处被推断为S&amp;&amp;,它将是SS&amp;)。

#include <utility>
#include <type_traits>
#include <iostream>

using namespace std;

struct Base {};

template<typename T>
struct Box : Base
{
    template<typename... Args>
    Box(Args&&... args)
        : t(forward<Args>(args)...)
    {
    }

    T t;
};

template<typename X>
Base* MakeBox(X&& x)
{
    return new Box<typename decay<X>::type>(forward<X>(x));
}

struct S
{
    S() { cout << "Default constructor" << endl; }
    S(S const& s) { cout << "Copy constructor" << endl; }
    S(S&& s) { cout << "Move constructor" << endl; }
    ~S() { cout << "Destructor" << endl; }
};

S foo()
{
    S s;
    return s;
}

int main()
{
    S s;

    // Invoking with lvalue, will deduce X=S&, argument will be of type S&
    MakeBox(s);

    // Invoking with rvalue, will deduce X=S, argument will be of type S&&
    MakeBox(foo());

    return 0;
}

如果您有兴趣,这里是 Scott Meyers 的一个很好的课程,他解释了通用引用的行为方式:

Scott Meyers on universal references

P.S.:这个答案已被编辑:我原来的答案建议使用std::remove_reference&lt;&gt;,但std::decay 原来是一个更好的选择。感谢问题发布者@Andrew Tomazos FathomlingCorps 指出了这一点,并感谢@Mankarse,他首先在对原始问题的评论中提出了这个问题。

【讨论】:

  • 确实std::decay 似乎是一个不错的选择,甚至可能更好,因为它也包含std::remove_reference&lt;&gt;。我一开始没有考虑它的原因是它去掉了const 限定符,但后来我意识到你可能根本不想保留const 限定符,因为你正在里面创建一个新对象Box 并调用其复制构造函数。如果是这样,我们确实可以在我的回答中将std::remove_reference 替换为std::decay。我会这样做并给予适当的信任。
  • 顺便说一句,关于函数MakeBox&lt;&gt;()和类Box&lt;&gt;,你确定你一直只想调用T的复制构造函数吗? Box&lt;&gt; 的构造函数中有一个参数包,用于将参数转发给 T 的构造函数,但您总是在输入中只传递一个 T 对象,因此它总是调用复制构造函数。你不应该在MakeBox&lt;&gt;() 中也有一个可变参数列表吗?顺便说一句,我不会返回 Base*,而是返回确切的指针 (Box&lt;typename std::decay&lt;T&gt;::type&gt;*)
  • 我认为typename decay&lt;typename remove_reference&lt;T&gt;::type&gt;::type; 在那里是多余的,因为decay&lt;&gt; 已经包含remove_reference&lt;&gt;。见这里:en.cppreference.com/w/cpp/types/decay
【解决方案2】:

对于提供的示例(1) 执行您想要的操作并存储该值。对于其他情况(例如当x 是一个数组时),您可以使用std::decay 将其衰减为一个指针并存储它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-07
    • 2013-11-28
    • 1970-01-01
    • 1970-01-01
    • 2020-06-28
    • 2020-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多