【问题标题】:Assert that code does NOT compile断言代码不能编译
【发布时间】:2014-06-26 04:52:26
【问题描述】:

简而言之:

如何编写一个测试,检查我的课程不可复制或可复制分配,而只能移动和移动分配?

一般:

如何编写测试,以确保特定代码不会编译?像这样:

// Movable, but non-copyable class
struct A
{
  A(const A&) = delete;
  A(A&&) {}
};

void DoCopy()
{
  A a1;
  A a2 = a1;
}

void DoMove()
{
  A a1;
  A a2 = std::move(a1);
}

void main()
{
  // How to define these checks?
  if (COMPILES(DoMove)) std::cout << "Passed" << std::endl;
  if (DOES_NOT_COMPILE(DoCopy)) std::cout << "Passed" << std::endl;
}

我猜想与 SFINAE 有关,但是否有一些现成的解决方案,也许在提升?

【问题讨论】:

  • if (DOES_NOT_COMPILE(DoCopy)) std::cout &lt;&lt; "Passed" &lt;&lt; std::endl; 不编译的工作量是多少?
  • 相关:如何对故意编译错误进行单元测试?在stackoverflow.com/questions/7282350/…
  • @user1810087 这就是他要问的......

标签: c++ c++11 boost compiler-errors sfinae


【解决方案1】:
template<class T>struct sink{typedef void type;};
template<class T>using sink_t=typename sink<T>::type;

template<typename T, typename=void>struct my_test:std::false_type{};
template<typename T>struct my_test<T,
  sink_t<decltype(

把代码放在这里。请注意,它必须“提前失败”,即在函数的签名中,而不是在主体中

  )>
>:std::true_type {};

如果可以评估“将代码放在这里”,上面会生成一个测试。

要确定是否无法评估“将代码放在这里”,请否定测试结果。

template<class T>using not_t=std::integral_constant<bool, !T::value>;
not_t< my_test< int > >::value

如果“将代码放在这里”在替换阶段失败,则为真。 (或者您可以通过交换上面的std::true_typestd::false_type 来进行更多手动操作)。

替换阶段的失败与一般的失败不同,因为它必须是一种表达方式,所以你能做的有些有限。但是,要测试是否可以复制,您可以这样做:

template<typename T, typename=void>struct copy_allowed:std::false_type{};
template<typename T>struct copy_allowed<T,
  sink_t<decltype(
    T( std::declval<T const&>() )
  )>
>:std::false_type {};

然后移动:

template<typename T, typename=void>struct move_allowed:std::false_type{};
template<typename T>struct move_allowed<T,
  sink_t<decltype(
    T( std::declval<T>() )
  )>
>:std::false_type {};

并且只移动:

template<typename T>struct only_move_allowed:
  std::integral_constant<bool, move_allowed<T>::value && !copy_allowed<T>::value >
{};

上述通用技术依赖于 SFINAE。基本特征类如下所示:

template<class T, typename=void> struct whatever:std::false_type{};

在这里,我们采用T 类型和我们默认为void 的第二个(匿名)参数。在工业强度库中,我们会将其隐藏为实现细节(公共 trait 会转发给这种私有 trait。

然后我们专攻。

template<typename T>struct whatever<T, /*some type expression*/>:std::true_type{};

诀窍在于,当且仅当我们希望我们的测试通过时,我们才会将 /*some type expression*/ 评估为类型 void。如果失败,我们可以评估为非void 类型,或者只是发生替换失败。

当且仅当它的计算结果为 void 时,我们才会得到 true_type

sink_t&lt; some type expression&gt; 技术采用 any 类型表达式并将其转换为void:基本上它是对替换失败的测试。图论中的sink是指事物流入的地方,什么也没有流出——在这种情况下,void什么都没有,类型流入它。

对于类型表达式,我们使用decltype( 一些非类型表达式),这让我们可以在“假”上下文中对其进行评估,我们只是丢弃结果。出于 SFINAE 的目的,现在评估非类型表达式。

请注意,MSVC 2013 对该特定步骤的支持有限或不支持。他们称之为“表达 SFINAE”。必须使用替代技术。

非类型表达式对其类型进行评估。它实际上并没有运行,也不会导致任何 ODR 使用。所以我们可以使用std::declval&lt;X&gt;() 来生成X 类型的“假”实例。我们将X&amp; 用于左值,X 用于右值,X const&amp; 用于const 左值。

【讨论】:

  • 我喜欢 C++ 的表现力。
  • @LightnessRacesinOrbit 是的——“不是给语言增加魔法,而是增加支持,这样你就可以用这种语言写魔法”的决定继续带来好处。
  • 这很好,但我们可以将它打包成类似 OP 要求的 COMPILES 之类的东西吗? std::string x; if (COMPILES(foo(x) + bar(1,2,3)){ .. }.
  • 看起来这不适用于案例,当作为 decltype 参数的表达式不能(也不应该)编译时。示例:coliru.stacked-crooked.com/a/ee9a0acc5b6db902
  • @fk0 接收器测试必须依赖于 my_test 中使用的类型 T 才能工作。无论T 是什么,一个总是失败的测试算不上什么测试。
【解决方案2】:

您正在寻找在&lt;type_traits&gt; 中定义的type traits,以测试类型是否具有某些属性。

【讨论】:

  • 太棒了!那么一般问题呢? IE。如何检查任意代码?
  • 你不应该有不能编译的代码。我假设您真正想要的是测试类型的属性,而不是实际测试代码是否无法编译。如果代码不能编译,你的编译器肯定会告诉你的,对吧?
  • 嗯,目前是的,你的回答对我来说已经足够了。我只是好奇。
  • 我相信这是不可能的,设计使然。如果您的代码无法编译,那么您将无法运行它。因此,您的支票将永远不会被执行。但好消息是,如果代码无法编译,您的编译器会告诉您原因,所以基本上编译器已经在为您进行检查。或类似的东西。
  • 编译器是唯一知道某个代码块是否可以编译的实体。 (例如,不完全符合标准的编译器将无法编译完美的标准代码 - 这是唯一可能知道这一点的东西。)因此,与其询问是否有某种方法可以检查某些东西是否可以编译,看看你能不能把reframe这个问题变成更可行的东西。例如:使用std::is_move_assignable&lt;A&gt;::value &amp;&amp; std::is_move_constructible&lt;A&gt;::value
【解决方案3】:

如果目标是确保代码不会编译,则不能 将其作为测试程序的一部分,否则,您的测试 程序不会编译。您必须在其上调用编译器, 看看返回码是什么。

【讨论】:

  • 为什么不呢?失败这里的一个选项;事实上,这是唯一的选择。如果程序无法编译,则测试成功。诀窍是确保编译因正确的原因而失败。
  • @DavidHammen 也许我不清楚。如果这样的测试是您的测试套件的一部分,则它不能嵌入到测试程序中;测试程序需要运行一个单独的进程,调用编译器,测试返回状态,并最终验证错误消息(但这可能非常困难,因为它们从一个编译器到另一个编译器差异很大)。
  • 我认为这里的正确方法,如前所述,是使用类型特征。这将确保该类型正在做它应该做的事情,并且可以以正常的方式进行测试,例如使用单元测试框架。
  • @GermánDiago 问题是要确保不应该编译的东西不会编译。你不能用类型特征来做到这一点。你必须编译。任何体面的测试工具都将提供一种测试外部代码的方法。
  • @James Kanze 我的理解是,如果我错了,请纠正我,如果特征编译为假(is_move_constructible ...),如果您尝试移动构造,则代码将无法编译。所以在这种情况下,你检查你想要的行为:如果你移动构造一个类型,那么它就不会编译,如果我理解正确的话,这就是全部目的。
【解决方案4】:

Andrzej Krzemieński 的一篇精彩文章“Diagnosable validity”的结尾给出了一个很好的答案:

检查给定构造是否编译失败的一种实用方法是从 C++ 外部进行:准备一个带有错误构造的小测试程序,编译它,并测试编译器是否报告编译失败。这就是“负面”单元测试与 Boost.Build 一起工作的方式。举个例子,见这个否定测试表单 Boost.Optional 库: optional_test_fail_convert_from_null.cpp。在配置文件中注解为compile-fail,表示只有编译失败才通过测试。

【讨论】:

    【解决方案5】:

    例如,这个std::is_nothrow_move_assignable&lt;std::string&gt;::value 在编译时返回true

    更多跳棋见https://en.cppreference.com/w/cpp/types#Supported_operations

    我建议将它与static_assert 一起使用,请参阅https://en.cppreference.com/w/cpp/language/static_assert

    现在一般 我试图检查我是否可以在某个对象上调用特定方法。这归结为“断言如果此代码编译”,并且有一种简洁而简短的方法来检查它。

    template<typename T> using canCallPrintYesOn = decltype(::std::declval<T>().printYes());
    
    constexpr bool canCallPrintYesOn_MyType = std::experimental::is_detected<canCallPrintYesOn, MyType>::value;
    
    static_assert(canCallPrintYesOn_MyType, "Should be able to call printYes(void) on this object");
    

    如果失败,上面的字符串会出现编译错误

    【讨论】:

      【解决方案6】:

      您可能需要稍微不同地构建代码才能使用它,但听起来您可能正在寻找

      static_assert (bool_constexpr, message)

      Performs compile-time assertion checking (C++11 起): 说明: bool_constexpr - 一个常量表达式 上下文可转换为布尔值; message - 字符串字面量 如果 bool_constexpr 为 false,则显示为 编译器错误。 静态断言声明可能出现在块范围内(作为块 声明)和类体内(作为成员声明)

      【讨论】:

      • 不,这不是我要找的。​​span>
      猜你喜欢
      • 1970-01-01
      • 2023-01-03
      • 1970-01-01
      • 1970-01-01
      • 2013-02-14
      • 1970-01-01
      • 1970-01-01
      • 2022-12-10
      • 2017-11-05
      相关资源
      最近更新 更多