娱乐网站设计SEO,网站处于建设中会显示什么英文,老牌深圳公司大雨中解散,南阳网站建设费用编译期if语句 if constexpr 编译期if语句使用编译期if语句编译期if的注意事项编译期if影响返回值类型即使在 *then* 部分返回也要考虑 *else* 部分编译期短路求值 其他编译期if的示例完美返回泛型值使用编译期if进行类型分发 带初始化的编译期if语句在模板之外使用编译期if参考… 编译期if语句 if constexpr 编译期if语句使用编译期if语句编译期if的注意事项编译期if影响返回值类型即使在 *then* 部分返回也要考虑 *else* 部分编译期短路求值 其他编译期if的示例完美返回泛型值使用编译期if进行类型分发 带初始化的编译期if语句在模板之外使用编译期if参考 通过使用语法if constexpr(...)编译器可以计算编译期的条件表达式来在编译期决定使用一个if语句的 then 的部分还是 else 的部分。其余部分的代码将会被丢弃这意味着它们甚至不会被生成。然而这并不意味着被丢弃的部分完全被忽略这些部分中的代码也会像没使用的模板一样进行语法检查。
例如
#include stringtemplate typename T
std::string asString(T x)
{if constexpr(std::is_same_vT, std::string) {return x; // 如果T不能自动转换为string该语句将无效}else if constexpr(std::is_arithmetic_vT) {return std::to_string(x); // 如果T不是数字类型该语句将无效}else {return std::string(x); // 如果不能转换为string该语句将无效}
}通过使用if constexpr我们在编译期就可以决定我们是简单返回传入的字符串、对传入的数字调用to_string()还是使用构造函数来把传入的参数转换为std::string。无效的调用将被 丢弃 因此下面的代码能够通过编译如果使用运行时if语句则不能通过编译
#include iostreamint main()
{std::cout asString(42) \n;std::cout asString(std::string(hello)) \n;std::cout asString(hello) \n;
}预编译代码如下
//代码std::cout asString(42) \n;的预编译代码如下
/* First instantiated from: insights.cpp:19 */
#ifdef INSIGHTS_USE_TEMPLATE
template
std::basic_stringchar, std::char_traitschar, std::allocatorchar asStringint(int x)
{if constexpr(false) {} else /* constexpr */ {if constexpr(true) {return std::to_string(x);} } }
#endif//代码std::cout asString(std::string(hello)) \n;的预编译代码如下
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template
std::basic_stringchar, std::char_traitschar, std::allocatorchar asStringstd::basic_stringchar, std::char_traitschar, std::allocatorchar (std::basic_stringchar, std::char_traitschar, std::allocatorchar x)
{if constexpr(true) {return std::basic_stringchar, std::char_traitschar, std::allocatorchar (static_caststd::basic_stringchar, std::char_traitschar, std::allocatorchar (x));}
}
#endif//代码std::cout asString(hello) \n;的预编译代码如下
/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template
std::basic_stringchar, std::char_traitschar, std::allocatorchar asStringconst char *(const char * x)
{if constexpr(false) {} else /* constexpr */ {if constexpr(false) {} else /* constexpr */ {return std::basic_stringchar, std::char_traitschar, std::allocatorchar (std::basic_stringchar, std::char_traitschar, std::allocatorchar (x, std::allocatorchar()));} }
}
#endifint main()
{std::operator(std::operator(std::cout, asString(42)), \n);std::operator(std::operator(std::cout, asString(std::basic_stringchar, std::char_traitschar, std::allocatorchar (std::basic_stringchar, std::char_traitschar, std::allocatorchar (hello, std::allocatorchar())))), \n);std::operator(std::operator(std::cout, asString(hello)), \n);return 0;
}编译期if语句
如果我们在上面的例子中使用运行时if下面的代码将永远不能通过编译
#include stringtemplate typename T
std::string asString(T x)
{if (std::is_same_vT, std::string) {return x; // 如果不能自动转换为string会导致ERROR}else if (std::is_numeric_vT) {return std::to_string(x); // 如果不是数字将导致ERROR}else {return std::string(x); // 如果不能转换为string将导致ERROR}
}
这是因为模板在实例化时整个模板会作为一个整体进行编译。然而if语句的条件表达式的检查是运行时特性。即使在编译期就能确定条件表达式的值一定是false then 的部分也必须能通过编译。因此当传递一个std::string或者字符串字面量时会因为std::to_string()无效而导致编译失败。此外当传递一个数字值时将会因为第一个和第三个返回语句无效而导致编译失败。
使用编译期if语句时 then 部分和 else 部分中不可能被用到的部分将成为 丢弃的语句
当传递一个std::string时第一个if语句的 else 部分将被丢弃。
template
std::basic_stringchar, std::char_traitschar, std::allocatorchar asStringstd::basic_stringchar, std::char_traitschar, std::allocatorchar (std::basic_stringchar, std::char_traitschar, std::allocatorchar x)
{if constexpr(true) {return std::basic_stringchar, std::char_traitschar, std::allocatorchar (static_caststd::basic_stringchar, std::char_traitschar, std::allocatorchar (x));} }当传递一个数字时第一个if语句的 then 部分和最后的 else 部分将被丢弃。
#ifdef INSIGHTS_USE_TEMPLATE
template
std::basic_stringchar, std::char_traitschar, std::allocatorchar asStringint(int x)
{if constexpr(false) {} else /* constexpr */ {if constexpr(true) {return std::to_string(x);} }
}
#endif当传递一个字符串字面量类型为const char*时第一和第二个if语句的 then 部分将被丢弃。
#ifdef INSIGHTS_USE_TEMPLATE
template
std::basic_stringchar, std::char_traitschar, std::allocatorchar asStringconst char *(const char * x)
{if constexpr(false) {} else /* constexpr */ {if constexpr(false) {} else /* constexpr */ {return std::basic_stringchar, std::char_traitschar, std::allocatorchar (std::basic_stringchar, std::char_traitschar, std::allocatorchar (x, std::allocatorchar()));} }
}
#endif因此在每一个实例化中无效的分支都会在编译时被丢弃所以代码能成功编译。注意被丢弃的语句并不是被忽略了。即使是被忽略的语句也必须符合正确的语法并且所有和模板参数无关的调用也必须正确。事实上模板编译的第一个阶段 定义期间 将会检查语法和所有与模板无关的名称是否有效。所有的static_asserts也必须有效即使所在的分支没有被编译。
例如
templatetypename T
void foo(T t)
{if constexpr(std::is_integral_vT) {if (t 0) {foo(t-1); // OK}}else {undeclared(t); // 如果未被声明且未被丢弃将导致错误undeclared(); // 如果未声明将导致错误即使被丢弃也一样static_assert(false, no integral); // 总是会进行断言即使被丢弃也一样}
}对于一个符合标准的编译器来说上面的例子 永远 不能通过编译,报错如下
source:17:9: error: there are no arguments to undeclared that depend on a template parameter, so a declaration of undeclared must be available [-fpermissive]17 | undeclared(); // 如果未声明将导致错误即使被丢弃也一样| ^~~~~~~~~~
source:17:9: note: (if you use -fpermissive, G will accept your code, but allowing the use of an undeclared name is deprecated)
source:18:23: error: static assertion failed: no integral18 | static_assert(false, no integral); // 总是会进行断言即使被丢弃也一样原因有两个
即使T是一个整数类型如下调用
undeclared(); // 如果未声明将导致错误即使被丢弃也一样如果该函数未定义时即使处于被丢弃的 else 部分也会导致错误因为这个调用并不依赖于模板参数。
如下断言
static_assert(false, no integral); // 总是会进行断言即使被丢弃也一样即使处于被丢弃的 else 部分也总是会断言失败因为它也不依赖于模板参数。一个使用编译期条件的静态断言没有这个问题
static_assert(!std::is_integral_vT, no integral);注意有一些编译器例如Visual C 2013和2015并没有正确实现模板编译的两个阶段。它们把第一个阶段 定义期间 的大部分工作推迟到了第二个阶段 实例化期间 因此有些无效的函数调用甚至一些错误的语法都可能通过编译。
使用编译期if语句
理论上讲只要条件表达式是编译期的表达式你就可以像使用运行期if一样使用编译期if。你也可以混合使用编译期和运行期的if
if constexpr (std::is_integral_vstd::remove_reference_tT) {if (val 10) {if constexpr (std::numeric_limitschar::is_signed) {...}else {...}}else {...}
}
else {...
}注意你不能在函数体之外使用if constexpr。因此你不能使用它来替换预处理器的条件编译。
编译期if的注意事项
使用编译期if时可能会导致一些并不明显的后果。
编译期if影响返回值类型
编译期if可能会影响函数的返回值类型。例如下面的代码总能通过编译但返回值的类型可能会不同
auto foo()
{if constexpr (sizeof(int) 4) {return 42;}else {return 42u;}
}这里因为我们使用了auto返回值的类型将依赖于返回语句而执行哪条返回语句又依赖于int的字节数
如果大于4字节返回42的返回语句将会生效因此返回值类型是int。否则返回42u的返回语句将生效因此返回值类型是unsigned int。 预处理代码如下
unsigned int foo()
{if constexpr(false) {return 42;} else /* constexpr */ {return 42U;} }这种情况下有if constexpr语句的函数可能返回完全不同的类型。例如如果我们不写 else 部分返回值将会是int或者void
auto foo() // 返回值类型可能是int或者void
{if constexpr (sizeof(int) 4) {return 42;}
}预处理代码如下
void foo()
{if constexpr(false) {return 42;} }
//或者
int foo()
{if constexpr(true) {return 42;} }注意这里如果使用运行期if那么代码将永远不能通过编译因为推导返回值类型时会考虑到所有可能的返回值类型因此推导会有歧义。 例如
#include iostream
#include string
#include utilityusing namespace std;auto foo() {if (sizeof(int) 4) {return 42;} else {return 42u;}
}int main() { return 0; }会有如下报错
source:13:16: error: inconsistent deduction for auto return type: int and then unsigned int13 | return 42u;| ^~~即使在 then 部分返回也要考虑 else 部分
运行期if有一个模式不能应用于编译期if如果代码在 then 和 else 部分都会返回那么在运行期if中你可以跳过else部分。也就是说
if (...) {return a;
}
else {return b;
}可以写成
if (...) {return a;
}
return b;但这个模式不能应用于编译期if因为在第二种写法里返回值类型将同时依赖于两个返回语句而不是依赖其中一个这会导致行为发生改变。例如如果按照上面的示例修改代码那么 也许能也许不能 通过编译
auto foo()
{if constexpr (sizeof(int) 4) {return 42;}return 42u;
}如果条件表达式为trueint大于4字节编译器将会推导出两个不同的返回值类型这会导致错误。否则将只会有一条有效的返回语句因此代码能通过编译。 可能的报错如下
source:11:12: error: inconsistent deduction for auto return type: int and then unsigned int11 | return 42u;| ^~~编译期短路求值
考虑如下代码
templatetypename T
constexpr auto foo(const T val)
{if constexpr(std::is_integralT::value) {if constexpr (T{} 10) {return val * 2;}}return val;
}这里我们使用了两个编译期条件来决定是直接返回传入的值还是返回传入值的两倍。
下面的代码都能编译
constexpr auto x1 foo(42); // 返回84
constexpr auto x2 foo(hi); // OK返回hi预处理代码如下
#ifdef INSIGHTS_USE_TEMPLATE
template
inline constexpr int fooint(const int val)
{if constexpr(true) {if constexpr(true) {return val * 2;} } return val;
}
#endif#ifdef INSIGHTS_USE_TEMPLATE
template
inline constexpr const char * foochar[3](const char (val)[3])
{if constexpr(false) {} return val;
}
#endifint main()
{constexpr const int x1 foo(42);constexpr const char *const x2 foo(hi);return 0;
}运行时if的条件表达式会进行短路求值当左侧为false时停止求值当||左侧为true时停止求值。这可能会导致你希望编译期if也会短路求值
templatetypename T
constexpr auto bar(const T val)
{if constexpr (std::is_integralT::value T{} 10) {return val * 2;}return val;
}然而编译期if的条件表达式总是作为整体实例化并且必须整体有效这意味着如果传递一个不能进行10运算的类型将不能通过编译
constexpr auto x2 bar(hi); // 编译期ERROR报错信息如下
source: In instantiation of constexpr auto bar(const T) [with T char [3]]:
source:17:28: required from here
source:10:53: error: taking address of temporary array10 | if constexpr (std::is_integralT::value T{} 10) {| ~~~~^~~~
source:11:20: error: invalid operands of types const char [3] and int to binary operator*11 | return val * 2;| ~~~~^~~
source: In function int main():
source:17:28: error: constexpr auto bar(const T) [with T char [3]] called in a constant expression17 | constexpr auto x2 bar(hi);| ~~~^~~~~~
source:8:16: note: constexpr auto bar(const T) [with T char [3]] is not usable as a constexpr function because:8 | constexpr auto bar(const T val)| 因此编译期if在实例化时并不短路求值。如果后边的条件的有效性依赖于前边的条件那你需要把条件进行嵌套。例如你必须写成如下形式
if constexpr (std::is_same_vMyType, T) {if constepxr (T::i 42) {...}
}而不是写成
if constexpr (std::is_same_vMyType, T T::i 42) {...
}其他编译期if的示例
完美返回泛型值
编译期if的一个应用就是先对返回值进行一些处理再进行完美转发。因为decltype(auto)不能推导为void因为void是不完全类型所以你必须像下面这么写
#include functional // for std::forward()
#include functional // for std::forward()
#include iostream
#include type_traits // for std::is_same and std::invoke_resultusing namespace std;double foo1(int x, int y) { return x * 2.5 y; }
void foo2() { cout foo2 endl; }template typename Callable, typename... Args
decltype(auto) call(Callable op, Args... args) {if constexpr (std::is_void_vstd::invoke_result_tCallable, Args...) {// 返回值类型是voidop(std::forwardArgs(args)...);return;} else {// 返回值类型不是void:decltype(auto) ret{op(std::forwardArgs(args)...)};return ret;}
}
int main() {double tv1 call(foo1, 5, 3);cout tv1 endl;call(foo2);return 0;
}函数的返回值类型可以推导为void但ret的声明不能推导为void因此必须把op返回void的情况单独处理。 运行结果如下
15.5
foo2预编译代码如下
#include functional // for std::forward()
#include iostream
#include type_traits // for std::is_same and std::invoke_resultusing namespace std;double foo1(int x, int y)
{return (static_castdouble(x) * 2.5) static_castdouble(y);
}void foo2()
{std::operator(std::cout, foo2).operator(std::endl);
}templatetypename Callable, typename ... Args
decltype(auto) call(Callable op, Args ... args)
{if constexpr(std::is_void_vstd::invoke_result_tCallable, Args... ) {op(std::forwardArgs(args)... );return;} else /* constexpr */ {decltype(auto) ret {op(std::forwardArgs(args)... )};return ret;} }#ifdef INSIGHTS_USE_TEMPLATE
template
double calldouble (*)(int, int), int, int(double (*op)(int, int), int __args1, int __args2)
{if constexpr(false) {} else /* constexpr */ {double ret {op(std::forwardint(__args1), std::forwardint(__args2))};return ret;} }
#endif#ifdef INSIGHTS_USE_TEMPLATE
template
void callvoid (*)()(void (*op)())
{if constexpr(true) {op();return;} }
#endifint main()
{double tv1 call(foo1, 5, 3);std::cout.operator(tv1).operator(std::endl);call(foo2);return 0;
}使用编译期if进行类型分发
编译期if的一个典型应用是类型分发。在C17之前你必须为每一个想处理的类型重载一个单独的函数。现在有了编译期if你可以把所有的逻辑放在一个函数里。
例如如下的重载版本的std::advance()算法
templatetypename Iterator, typename Distance
void advance(Iterator pos, Distance n) {using cat std::iterator_traitsIterator::iterator_category;advanceImpl(pos, n, cat{}); // 根据迭代器类型进行分发
}templatetypename Iterator, typename Distance
void advanceImpl(Iterator pos, Distance n, std::random_access_iterator_tag) {pos n;
}templatetypename Iterator, typename Distance
void advanceImpl(Iterator pos, Distance n, std::bidirectional_iterator_tag) {if (n 0) {while (n--) {pos;}}else {while (n) {--pos;}}
}templatetypename Iterator, typename Distance
void advanceImpl(Iterator pos, Distance n, std::input_iterator_tag) {while (n--) {pos;}
}现在可以把所有实现都放在同一个函数中
templatetypename Iterator, typename Distance
void advance(Iterator pos, Distance n) {using cat std::iterator_traitsIterator::iterator_category;if constexpr (std::is_convertible_vcat, std::random_access_iterator_tag) {pos n;}else if constexpr (std::is_convertible_vcat, std::bidirectional_access_iterator_tag) {if (n 0) {while (n--) {pos;}}else {while (n) {--pos;}}}else { // input_iterator_tagwhile (n--) {pos;}}
}这里我们就像是有了一个编译期switch每一个if constexpr语句就像是一个case。然而注意例子中的两种实现还是有一处不同的
重载函数的版本遵循 最佳匹配 语义。编译期if的版本遵循 最先匹配 语义。
另一个类型分发的例子是使用编译期if实现get()重载来实现结构化绑定接口。 第三个例子是在用作std::variant访问器的泛型lambda中处理不同的类型。
带初始化的编译期if语句
注意编译期if语句也可以使用新的带初始化的形式。例如如果有一个constexpr函数foo()你可以这样写
#include functional // for std::forward()
#include iostream
#include type_traits // for std::is_same and std::invoke_resultusing namespace std;double foo(int x) { return x * 2.5; }template typename T
void bar(const T x) {if constexpr (auto obj foo(x); std::is_same_vdecltype(obj), T) {std::cout foo(x) yields same type\n;} else {std::cout foo(x) yields different type\n;}
}
int main() {bar(2);bar(2.5);return 0;
}运行结果如下
foo(x) yields different type
foo(x) yields same type预处理代码如下
#include functional // for std::forward()
#include iostream
#include type_traits // for std::is_same and std::invoke_resultusing namespace std;double foo(int x)
{return static_castdouble(x) * 2.5;
}templatetypename T
void bar(const T x)
{{auto obj foo(x);if constexpr(std::is_same_vdecltype(obj), T) {std::operator(std::cout, foo(x) yields same type\n);} else /* constexpr */ {std::operator(std::cout, foo(x) yields different type\n);} }}
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template
void barint(const int x)
{{double obj foo(x);if constexpr(false) {} else /* constexpr */ {std::operator(std::cout, foo(x) yields different type\n);} }
}
#endif/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template
void bardouble(const double x)
{{double obj foo(static_castint(x));if constexpr(true) {std::operator(std::cout, foo(x) yields same type\n);} }}
#endif
int main()
{bar(2);bar(2.5);return 0;
}如果有一个参数类型也为T的constexpr函数foo()你就可以根据foo(x)是否返回与x相同的类型来进行不同的处理。 如果要根据foo(x)返回的值来进行判定那么可以这么写
#include functional // for std::forward()
#include iostream
#include type_traits // for std::is_same and std::invoke_resultusing namespace std;constexpr int foo(int x) { return x * 2; }void bar() {constexpr auto c 2;if constexpr (constexpr auto obj foo(c); obj 4) {std::cout foo(c) obj endl;} else {std::cout foo(x) yields different type\n;}
}
int main() {bar();return 0;
}注意如果想在条件语句中使用obj的值那么obj必须要声明为constexpr。运行结果如下
foo(c) 4预处理代码如下
#include functional // for std::forward()
#include iostream
#include type_traits // for std::is_same and std::invoke_resultusing namespace std;inline constexpr int foo(int x)
{return x * 2;
}void bar()
{constexpr const int c 2;{constexpr const int obj foo(c);if constexpr(true) {std::operator(std::operator(std::operator(std::cout, foo(), c), ) ).operator(obj).operator(std::endl);} else /* constexpr */ {std::operator(std::cout, foo(x) yields different type\n);} }
}
int main()
{bar();return 0;
}在模板之外使用编译期if
if constexpr可以在任何函数中使用而并非仅限于模板。只要条件表达式是编译期的并且可以转换成bool类型。然而在普通函数里使用时 then 和 else 部分的所有语句都必须有效即使有可能被丢弃。
例如下面的代码不能通过编译因为undeclared()的调用必须是有效的即使char是有符号数导致 else 部分被丢弃也一样
#include limitstemplatetypename T
void foo(T t);int main()
{if constexpr(std::numeric_limitschar::is_signed) {foo(42); // OK}else {undeclared(42); // 未声明时总是ERROR即使被丢弃}
}下面的代码也永远不能成功编译因为总有一个静态断言会失败
if constexpr(std::numeric_limitschar::is_signed) {static_assert(std::numeric_limitschar::is_signed);
}
else {static_assert(!std::numeric_limitschar::is_signed);
}在泛型代码之外使用编译期if的唯一好处是被丢弃的部分不会成为最终程序的一部分这将减小生成的可执行程序的大小。例如在如下程序中
#include limits
#include string
#include arrayint main()
{if (!std::numeric_limitschar::is_signed) {static std::arraystd::string, 1000 arr1;...}else {static std::arraystd::string, 1000 arr2;...}
}要么arr1要么arr2会成为最终可执行程序的一部分但不可能两者都是。
参考
http://www.cppstd17.com/