如果 constexpr 失败,为什么 C++17 的这种用法会失败?

cow*_*cow 30 c++ templates if-statement constexpr c++17

我正在尝试使用 C++17if constexpr进行条件编译,但它的行为并不符合我的预期。

比如下面的代码,C++还是编译宏定义的代码X2

#include <map>
#include <string>
#include <iostream>
#include <type_traits>

#define X1 pp("x")
#define X2 pp("x", "x")

void pp(const std::string str)
{
   std::cout << str << std::endl;
}

int main()
{
   std::map<std::string, int> map;

   if constexpr (std::is_null_pointer_v<decltype(map)>)
      X2;
   else
      X1;
}
Run Code Online (Sandbox Code Playgroud)

并吐出此错误消息:

#include <map>
#include <string>
#include <iostream>
#include <type_traits>

#define X1 pp("x")
#define X2 pp("x", "x")

void pp(const std::string str)
{
   std::cout << str << std::endl;
}

int main()
{
   std::map<std::string, int> map;

   if constexpr (std::is_null_pointer_v<decltype(map)>)
      X2;
   else
      X1;
}
Run Code Online (Sandbox Code Playgroud)

如何跳过X2的编译?

JeJ*_*eJo 36

这在模板之外是不可能的!

来自cppreference.com

在模板之外,完全检查丢弃的语句。 if constexpr不能替代#if预处理指令:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
Run Code Online (Sandbox Code Playgroud)

知道如何跳过X2 的编译吗?

一种选择是为此提供模板函数。
template<typename T>
void test()
{
   if constexpr (std::is_null_pointer_v<T>)
      X2;
   else
      X1;
}

int main()
{
   std::map<std::string, int> map;
   test<decltype(map)>();   // now chooses the X1
}
Run Code Online (Sandbox Code Playgroud)

感谢@HolyBlackCat@MSalters。正如他们所指出的,上述解决方案是格式错误的 NDR(因此,使用MSVC 编译器进行编译没有任何意义,另一方面,GCC 和 clang 至少通过提供一些编译器错误来捕捉到这一点),这已在@HolyBlackCat的,回答!

因此我们可以跳过X2的编译吗?

不幸的是,NO,按您的代码!该预处理器将翻译单元的编译之前执行。因此,不能decltype(map)#if指令提供类型信息(即)。因此,对于您的情况,别无他法。

这篇文章的好教训

  • 然而,你的程序是一个很好的例子,可以避免这种宏和constexpr if混合。
  • 其次,如果可能的话,通过不同的编译器检查代码的正确性!

我建议PP对您的情况进行函数重载(当然还有许多其他方法),通过它您可以获得格式良好的代码:

查看演示

#include <string>
#include <iostream>
#include <type_traits>
#include <map>

void pp(const std::string& str)
{
   std::cout << str << std::endl;
}

template<typename... T> void pp(const T&... args)
{
   // do something with args!
}

template<typename T>
constexpr void test()
{
   if constexpr (std::is_null_pointer_v<T>)
      pp("x", "x"); // call with args
   else
      pp("x"); // call with string
}
Run Code Online (Sandbox Code Playgroud)

  • @HolyBlackCat:为什么是 NDR?模板被实例化。似乎有 4 个“T”可以实例化测试(“std::nullptr_t”,加上 cv 限定的。) (4认同)
  • @HolyBlackCat 好的,谢谢,讨论很有趣。很有趣的是看到一个错误的答案有这么多的赞成票。我也没有检查就投票了,因为它看起来是正确的:p (3认同)
  • @JeJo 据我所知,MSVC 往往是三个主要编译器中最不合规的。:/ (3认同)
  • @HolyBlackCat 好的,我刚刚读完。我现在意识到我的错误了。非常感谢您指出一个大错误,在我看来,由于我的 MSVS,这也是正确的!让我更新我的答案! (2认同)

Hol*_*Cat 9

if constexpr 并不是真正的“条件编译”。

在模板之外,它的工作方式与常规一样if(除了它希望条件为constexpr)。

其他答案建议将它放在模板中(并使条件取决于模板参数),但这还不够。(它似乎在 MSVC 中有效,但在 GCC 和 Clang 中无效。)那是因为:

[temp.res]/8.1 (强调我的)

可以在任何实例化之前检查模板的有效性。... 程序格式错误,无需诊断,如果:

— 不能为模板模板中的 constexpr if 语句的子语句生成有效的特化,并且模板未实例化,...

因此,如果您不能为if constexpr分支进行有效的实例化(也就是说,如果对于所有可能的模板参数,分支都是无效的),那么该程序就是格式错误的 NDR(这实际上意味着“无效,但编译器可能不是足够聪明,可以给你一个错误”)。

(正如@MSalters 所指出的,标准说“并且模板没有被实例化”,而不是“如果没有被实例化,模板或 constexpr 的子语句”。我认为这是一个有缺陷的措辞,因为它没有意义否则:似乎没有被任何其他措辞来检查废弃树枝的有效性,所以它会使代码合式只有当封闭的模板实例化,并形成不良的NDR否则看到的。在评论中讨论)。

似乎没有解决方法,也没有针对您的问题的好的解决方案。

可以使函数调用本身依赖于模板参数,但这可能是作弊,因为它需要隐藏pp(或执行#define pp …)。

template <typename F>
void test(F pp) // Note parameter shadowing the global `pp` for the macros.
{
    std::map<std::string, int> map;

    if constexpr (std::is_null_pointer_v<decltype(map)>)
        X2;
    else
        X1;
}

int main()
{
    test([](auto &&... params)
    {
        pp(decltype(params)(params)...);
    });
}
Run Code Online (Sandbox Code Playgroud)

  • 我想你的答案是这是不可能的。我想我同意:)我不得不说,我真的不喜欢这个“解决方案”。隐藏宏的名称有点回避这个问题。 (2认同)

cig*_*ien 7

在模板之外,甚至if constexpr完全检查an 的错误分支。一般来说,为此,人们需要

  • 要么使用#if预处理器指令,
  • 或将if constexpr代码放入模板中。

在您的情况下,您不能使用,#if因为您的条件取决于预处理器不可用的类型信息。

此外,您不能使用任何可能的模板参数,constexpr if因为宏的扩展X2总是格式错误的。

您可能需要重新考虑为什么要拥有一个其扩展永远无效的宏。