如果 constexpr 失败,为什么要使用 C++17?
Why does this usage of C++17 if constexpr fail?
我正在尝试使用 C++17 if 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;
}
并吐出此错误消息:
1.c:6:23: error: too many arguments to function ‘void pp(std::__cxx11::string)’
#define X2 pp("x", "x")
^
1.c:18:3: note: in expansion of macro ‘X2’
X2;
^~
如何跳过X2的编译?
在模板之外,甚至 if constexpr
的错误分支也会被完全检查。一般来说,为此,需要
- 要么使用
#if
预处理器指令,
- 或将
if constexpr
代码放入模板中。
在您的情况下,您不能使用 #if
,因为您的条件取决于预处理器不可用的类型信息。
此外,您不能使用 constexpr if
,因为对于任何可能的模板参数,宏 X2
的扩展总是格式错误的。
您可能需要重新考虑为什么要使用扩展永远无效的宏。
这在模板之外是不可能的!
Outside a template, a discarded statement is fully checked. if constexpr
is not a substitute for the #if
preprocessing directive:
void f() {
if constexpr(false) {
int i = 0;
int *p = i; // Error even though in discarded statement
}
}
Any idea how to skip compilation of 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
}
感谢 @HolyBlackCat and @MSalters. As they pointed out, the above solution is ill-formed NDR (therefore, compiling with MSVC compiler 没有任何意义,另一方面
GCC and clang at least catch this 通过提供一些编译器错误)
中有详细说明,
回答!
所以我们可以跳过X2的编译吗?
不幸的是,根据您的代码,否!!
preprocessor 将在翻译单元编译之前执行。
因此不能向 #if
指令提供类型信息(即 decltype(map)
)。
因此,对于您的情况,没有其他办法。
很好的教训 post:
- 然而,您的程序是避免此类宏的一个很好的例子
和
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
}
if constexpr
并不是真正的“条件编译”。
在模板之外,它的工作方式与常规 if
相同(除了它希望条件为 constexpr
)。
其他答案建议将它放在模板中(并使条件取决于模板参数),但这还不够。 (它似乎在 MSVC 中有效,但在 GCC 和 Clang 中无效。)那是因为:
[temp.res]/8.1
(emphasis mine)
The validity of a template may be checked prior to any instantiation. ...
The program is ill-formed, no diagnostic required, if:
— no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, ...
因此,如果您不能为 if constexpr
分支进行有效实例化(也就是说,如果对于所有可能的模板参数,该分支都是无效的),那么该程序就是格式错误的 NDR(这实际上意思是“无效,但编译器可能不够聪明,不会给你一个错误”)。
(正如@MSalters 所指出的,标准说“并且模板未实例化”,而不是“和模板 或 constexpr 的子语句 if 未实例化”。我认为这是一个有缺陷的措辞,因为它没有其他意义:似乎没有任何其他措辞来检查丢弃分支的有效性,因此它会使代码格式正确 仅当 封闭模板 被 实例化,否则 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)...);
});
}
如果可能,您可以修改 pp
以按照我认为的方式运行:
void pp(auto const &...args){
((std::cout << std::forward<decltype(args)>(args)), ...) << std::endl;
}
如果这不可能,那么我建议将 X2 修复为至少可以编译的代码。
如果无法更改 X2,那么我建议在调用 x2 时隐藏 pp,尽管这当然是最好的解决方案:
if constexpr (std::is_null_pointer_v<decltype(map)>) {
auto pp = [] (auto const &...args){
((std::cout << std::forward<decltype(args)>(args)), ...) << std::endl;
};
X2;
} else {
X1;
}
我正在尝试使用 C++17 if 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;
}
并吐出此错误消息:
1.c:6:23: error: too many arguments to function ‘void pp(std::__cxx11::string)’
#define X2 pp("x", "x")
^
1.c:18:3: note: in expansion of macro ‘X2’
X2;
^~
如何跳过X2的编译?
在模板之外,甚至 if constexpr
的错误分支也会被完全检查。一般来说,为此,需要
- 要么使用
#if
预处理器指令, - 或将
if constexpr
代码放入模板中。
在您的情况下,您不能使用 #if
,因为您的条件取决于预处理器不可用的类型信息。
此外,您不能使用 constexpr if
,因为对于任何可能的模板参数,宏 X2
的扩展总是格式错误的。
您可能需要重新考虑为什么要使用扩展永远无效的宏。
这在模板之外是不可能的!
Outside a template, a discarded statement is fully checked.
if constexpr
is not a substitute for the#if
preprocessing directive:void f() { if constexpr(false) { int i = 0; int *p = i; // Error even though in discarded statement } }
Any idea how to skip compilation of 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
}
感谢 @HolyBlackCat and @MSalters. As they pointed out, the above solution is ill-formed NDR (therefore, compiling with MSVC compiler 没有任何意义,另一方面
GCC and clang at least catch this 通过提供一些编译器错误)
所以我们可以跳过X2的编译吗?
不幸的是,根据您的代码,否!!
preprocessor 将在翻译单元编译之前执行。
因此不能向 #if
指令提供类型信息(即 decltype(map)
)。
因此,对于您的情况,没有其他办法。
很好的教训 post:
- 然而,您的程序是避免此类宏的一个很好的例子
和
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
}
if constexpr
并不是真正的“条件编译”。
在模板之外,它的工作方式与常规 if
相同(除了它希望条件为 constexpr
)。
其他答案建议将它放在模板中(并使条件取决于模板参数),但这还不够。 (它似乎在 MSVC 中有效,但在 GCC 和 Clang 中无效。)那是因为:
[temp.res]/8.1
(emphasis mine)The validity of a template may be checked prior to any instantiation. ... The program is ill-formed, no diagnostic required, if:
— no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, ...
因此,如果您不能为 if constexpr
分支进行有效实例化(也就是说,如果对于所有可能的模板参数,该分支都是无效的),那么该程序就是格式错误的 NDR(这实际上意思是“无效,但编译器可能不够聪明,不会给你一个错误”)。
(正如@MSalters 所指出的,标准说“并且模板未实例化”,而不是“和模板 或 constexpr 的子语句 if 未实例化”。我认为这是一个有缺陷的措辞,因为它没有其他意义:似乎没有任何其他措辞来检查丢弃分支的有效性,因此它会使代码格式正确 仅当 封闭模板 被 实例化,否则 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)...);
});
}
如果可能,您可以修改 pp
以按照我认为的方式运行:
void pp(auto const &...args){
((std::cout << std::forward<decltype(args)>(args)), ...) << std::endl;
}
如果这不可能,那么我建议将 X2 修复为至少可以编译的代码。
如果无法更改 X2,那么我建议在调用 x2 时隐藏 pp,尽管这当然是最好的解决方案:
if constexpr (std::is_null_pointer_v<decltype(map)>) {
auto pp = [] (auto const &...args){
((std::cout << std::forward<decltype(args)>(args)), ...) << std::endl;
};
X2;
} else {
X1;
}