【问题标题】:Is it possible to implement always_false in the C++ standard library?是否可以在 C++ 标准库中实现 always_false?
【发布时间】:2020-01-07 07:18:24
【问题描述】:

在某些情况下,例如使用 always_false 助手如果尝试实例化某个模板,则会导致无条件的 static_assert 失败:

template <class... T> struct always_false : std::false_type {};

template<class T>
struct UsingThisShouldBeAnError {
  static_assert(always_false<T>::value, "You should not use this!");
};

这个助手是必要的,因为模板定义必须(至少在理论上)具有至少一组模板参数,可以为其生成有效的特化,以使程序具有良好的格式:

[temp.res]/8:程序格式错误,不需要诊断,如果:

  • 无法为模板生成有效的特化 [...] 并且模板未实例化,或者

[...]

(因此,在上面编写 static_assert(false, "You should not use this!"); 格式不正确,编译器总是可以触发静态断言,即使没有实例化模板,这不是本意。)

以下是涉及此模式的问题的快速示例(包括进一步的解释):

always_false 作为标准库中的工具可能会很有用,这样我们就不必不断地重新编写它。但是,以下问题的答案让我怀疑这是否可能:

Dependent non-type parameter packs: what does the standard say?

这里有一个论点(也关于 [temp.res]/8)std::enable_if_t&lt;T&gt; 始终要么是 void 要么不是类型,并且任何人进一步专门化它都是非法的。因此,依赖std::enable_if 的理论“特化性”来避免 [temp.res]/8 子句的模板实际上会导致程序格式错误,不需要诊断。

回到我的问题:如果标准提供了always_false,它就必须禁止图书馆用户像往常一样对其进行专业化(原因​​很明显)。但是根据上述推理,这将破坏always_false 的全部观点(即理论上它可以专门用于std::false_type 以外的其他东西) - 就 [temp.res]/8 而言,它与使用相同直接std::false_type

我的推理错了吗?或者标准库实际上不可能以有意义/有用的方式提供always_false(无需更改核心语言)?

【问题讨论】:

  • 标准库可以做在用户代码中非法的事情,所以我会说是的,标准库有可能实现这样的构造,即使它是非法的。不过不要引用我的话。
  • @bolov:问题是用户的使用。类定义很简单,没有问题。但是一旦进入std,就会对用户施加额外的限制(除非允许,否则没有专业化)。
  • @Jarod42 天哪,C++ 竟然有这么阴暗的角落……

标签: c++ templates template-specialization


【解决方案1】:

在 C++20 中,使用 lambda,您可能会执行以下操作:

template <class... T> struct always_false : std::false_type {};

// To have true, but for a type that user code can't reuse as lambda types are unique.
template <> struct always_false<decltype([](){})> : std::true_type{};

【讨论】:

  • 真的需要 C++20 吗?像std::_UglyTypeWeCannotTouch 这样的东西不会吗?
  • @StoryTeller: 可能,但不确定这样的类型不会违反要求“没有核心语言更改”
  • @Jarod42 template &lt;&gt; struct always_false&lt;/* implementation defined */&gt; : std::true_type{}; 又名未指定和隐藏怎么样,否则没有特殊意义。
  • bolov 说了什么。毕竟这就是_Identifiers 的用途。
  • @StoryTeller 这是你的主意。如果您愿意,请务必回答。
【解决方案2】:

套用 Jarod 的想法,可能是这样的

template <class... T> struct always_false : std::false_type {};

template <> struct always_false</* implementation defined */> : std::true_type{};

其中/* implementation defined */ 可以填写std::_ReservedIdentifer。用户代码无法访问它,因为标识符是为库保留的,但存在一个专门化为 true。这应该避免有关专业化中的 ODR 和 lambdas 的问题。

【讨论】:

  • 接受这个解决方案,因为它更具包容性(我猜更像标准)。
【解决方案3】:

所有此类尝试都会导致程序格式错误,无需诊断。

阻止您使用static_assert(false) 的子句使您的程序格式错误,无需根据进行实际实例化的实际可能性进行诊断,而不是根据编译器是否可以解决。

这些技巧只会让编译器更难检测您的程序格式错误的事实。他们发布的诊断不是必需的,并且您能够绕过所发布的诊断只是意味着您让编译器生成了一个格式错误的程序,标准对其行为没有任何限制。

(Writing static_assert(false, "You should not use this!"); 因此上面的格式不正确,编译器总是可以触发静态断言,即使没有实例化模板,这不是本意。)

同样的结论也适用于你的

template <class... T> struct always_false : std::false_type {};

template<class T>
struct UsingThisShouldBeAnError {
  static_assert(always_false<T>::value, "You should not use this!");
};

我声称上述程序中没有UsingThisShouldBeAnError 的有效实例化。

http://eel.is/c++draft/temp.res#6.1

程序格式错误,无需诊断,如果: (6.1) 无法为模板生成有效的特化 [...]"

无法为此模板生成有效的特化。

为了避免这个陷阱,你的程序必须

template <> struct always_false<SomeListOfTypes> : std::true_type {};

如果在标准中指定了 always_false 而不会发生这种情况,则使用标准中的 always_false 对您毫无帮助。 因为标准要求“可以生成”专业化。

如果实例化您的模板的唯一方法是在一个格式错误的程序中,那么这就是“可以”这个词的巨大延伸。因此,在 true_type 专业化中使用不允许使用的保留类型或魔法类型并不是很合理。

退后一步,“格式错误,ndr”的目的是因为标准编写者希望允许对损坏的代码进行诊断,但不想强制执行。检测这种损坏的代码通常是一个很难停止的问题,但可以检测到简单的情况。围绕代码未损坏的假设进行优化可能很有用。

所有注入static_assert(false, "message") 的尝试都围绕着意图使C++ 代码有效。

我们有一个函数的构造,对于该函数来说,成功查找是语言中的错误。

template<class T>
void should_never_be_found(tag<T>) = delete;

当然,漏洞在于这里缺少诊断信息。

template<class T>
void should_never_be_found(tag<T>) = delete("Provide an custom ADL overload!");

另外,无法=delete 模板特化。

你看起来想要的是:

template<class T>
struct UsingThisShouldBeAnError = delete("You should not use this");

这是直接的、有意的,并且与其他 =delete 案例的工作方式一致。

static_assert 绕过需要魔法并“欺骗”编译器以绕过明确编写的标准部分,以防止您做您想做的事情。

语言中缺少模板类上的 =delete("string")=delete 语法。

【讨论】:

  • “你的程序必须有”。可以添加多少代码才能使专业化有效?从你的回答看来,没有。如果不存在满足要求的类(template &lt;typename T&gt; void foo(T&amp; t) -&gt; decltype(t.foo()) { return t.foo(); } 可能是不正确的),您的意思是基于 type_traits(例如 has_foo)的 SFINAE 是不正确的 NDR。
  • 我同意您的整体评估以及您对整体问题的解决方案 (=delete) 的建议。然而,问题的重点是探索“如果在标准中指定了不会发生这种情况的always_false”的否定:我们如何可以指定@987654342 @ 允许它以有用的方式发生(接受与名称的矛盾)?我相信SomeListOfTypes = /*implementation defined*/ 符合“至少存在一种合法可用的类型导致true_type,只是故意很难命名它”。
  • @MaxLanghof 我们不能。允许用户在 C++ 代码中使用 SomeListOfTypes 中的类型,在这种情况下它不满足您的要求,或者用户代码不能,在这种情况下,模板没有有效的特化。请记住,std 头文件中的所有内容在 C++ 中简直就是魔法。它们中的哪些代码结构有效是不相关的。我的意思是,我们可以使用“在这里工作的类型的名称是实现定义的”,但是用户必须能够命名它并且使用它必须是有效的。
  • 任何和 all hack 要么是“我试图愚弄编译器”,要么是“我试图迷惑或说服用户”。从根本上欺骗编译器是行不通的,因为标准在这里是无所不知的。如果唯一有效的类型是在 Collat​​z 猜想为真时存在的类型,那么根据 Collat​​z 猜想的真值,您的程序是不正确的。
  • @Yakk-AdamNevraumont 关键是在“使用名称是通过惩罚格式不正确,NDR 来禁止使用该名称”和“有一种众所周知的方法可以让 std::always_false 成为 @ 987654349@"。同样,我同意这是次优的(并且在“迷惑或说服用户”的领域),但这就是生活。 std::array 还必须将其实际数据成员公开且可供用户访问,这样世界就不会崩溃。
【解决方案4】:

只需使用选项-fdelayed-template-parsing

【讨论】:

  • -fdelayed-template-parsing 是一些编译器启用非标准行为的选项。但我的问题是关于标准库的理论补充,同时保持符合标准的行为。它甚至与编译器无关。
猜你喜欢
  • 1970-01-01
  • 2011-03-18
  • 2021-10-27
  • 2016-08-23
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
  • 1970-01-01
  • 2019-12-17
相关资源
最近更新 更多