【问题标题】:how is this sizeof expression evaluated? and why it is called that way?如何评估这个 sizeof 表达式?为什么这样称呼它?
【发布时间】:2016-10-28 14:20:17
【问题描述】:

我在std::optional 实现中遇到了这段代码:

template <class T, class U>
struct is_assignable
{
  template <class X, class Y>
  constexpr static bool has_assign(...) { return false; }

  template <class X, class Y, size_t S = sizeof((std::declval<X>() = std::declval<Y>(), true)) >
  // the comma operator is necessary for the cases where operator= returns void
  constexpr static bool has_assign(bool) { return true; }

  constexpr static bool value = has_assign<T, U>(true);
};

我无法理解它是如何工作或如何评估它的部分是size_t S = sizeof((std::declval&lt;X&gt;() = std::declval&lt;Y&gt;(), true)) 我知道如果分配操作失败,它将回退到返回 false 的 has_assign 的第一个定义,但我不知道为什么它有 , true) 部分。

我对在分配运算符上返回 void 的结构进行了一些测试,并删除了 sizeof 中的 , true 部分给了我相同的结果。

【问题讨论】:

  • en.cppreference.com/w/cpp/language/sfinae 。如果sizeof 中的表达式对给定的XY 有效,则has_assign 有两个重载,而has_assign&lt;T, U&gt;(true) 则选择第二个重载作为更好的匹配。如果表达式无意义,则丢弃该重载,并选择第一个。
  • 一些编译器将sizeof(void) == 1定义为扩展。启用更多警告。
  • class S = decltype(std::declval&lt;X&gt;() = std::declval&lt;Y&gt;())会更直接。我们不管S是什么,只要那个表达式是有效的。
  • @SamVashavchik 我理解这个问题的方式,它不是关于 SFINAE。 OP 甚至说“我知道如果分配操作失败,它将回退到返回 false 的 has_assign 的第一个定义......”真正的问题是为什么使用逗号运算符 (, true),它几乎没有与 SFINAE 本身有关。
  • @Fanael 没有。仔细看看sizeof 里面的内容。这是sizeof(something=something, true)。我想如果我让X::operator=(Y)返回一个重载operator,()的类,它可能会被破坏。

标签: c++


【解决方案1】:

为了申请sizeof(),你需要一个完整的类型。但是返回一个完整的类型并不是可分配性的要求,因此:

sizeof((std::declval<X>() = std::declval<Y>(), true))
       ~~~~~~~~~~~~~~~~~~ expr ~~~~~~~~~~~~~~~~~~~~~

如果分配对这两种类型有效,那么我们就有sizeof(expr),其中expr 的类型是bool(因为true)。因此,如果分配有效,我们会得到一些真实的size。否则,替换失败。


但这是编写此代码的一种不必要的神秘方式。此外,它甚至不正确,因为我可以编写如下类型:

struct Evil {
    template <class T> Evil operator=(T&& ); // assignable from anything
    void operator,(bool);                    // mwahahaha
};

现在您的sizeof() 仍然无法使用。

相反,更喜欢简单:

class = decltype(std::declval<X>() = std::declval<Y>())

这实现了相同的结果 - 无论是否替换失败 - 根本不需要关心结果的类型或处理特殊情况。

【讨论】:

  • 原始代码是否可以在某些编译器上工作(可能较旧且不完全符合标准)?一种可能的优势可能在于生成的代码。由于sizeof(true) 总是会给出相同的大小(假设所有文件都使用兼容的选项编译),因此如果不进行内联,它将有助于确保只保留一个函数...
  • @Phil1970 这一切都是在编译时完成的。这些函数都不应该出现在目标文件中。
  • 请注意,在某些情况下 is_assignable(即使是标准的)也会给您带来误报。但我认为可以就此事提出一个新问题。 gist
  • @alter 那是因为Ex1::operator= 对它可以分配的类型撒谎。这不是std::is_assignable 的问题。
【解决方案2】:

原则上,表达式std::declval&lt;X&gt;() = std::declval&lt;Y&gt;()的类型(即所涉及的operator =的返回类型)可以是任意的,包括不完整类型或void。在这种情况下,SFINAE 不会启动,因为表达式是有效的。但是,如果将 sizeof 应用于不完整的类型,则会出现错误。 (请注意,一些编译器将sizeof(void) == 1 定义为扩展,但不能普遍依赖)。

在 SFINAE 表达式之后添加 , true 通过丢弃分配的类型(无论它是什么)来解决此问题,并改为将 sizeof 应用于 true(这是完全有效的)。

如 cmets 中的 Barry 所示,更直接的方法是使用 decltype 中的赋值类型而不是 sizeof 中的类型,如下所示:

template <class X, class Y, class S = decltype(std::declval<X>() = std::declval<Y>()) >
constexpr static bool has_assign(bool) { return true; }

【讨论】:

    猜你喜欢
    • 2013-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多