if constexpr 在模板化 lambda 中未丢弃的错误分支

Posted

技术标签:

【中文标题】if constexpr 在模板化 lambda 中未丢弃的错误分支【英文标题】:False-branch of if constexpr not discarded in templated lambda 【发布时间】:2018-04-10 14:04:29 【问题描述】:

我对模板化 lambda 中的“if constexpr”有疑问。为了争论,让我们忽略我是如何到达那里的,但我有一个以某种方式定义的 struct foo 导致如下所示:

template<bool condition>
struct foo 
    int a;

    // Only contains b if condition is true
    int b;

现在我可以定义一个模板函数 thtemplate

template<bool condition>
void print_fun(foo & obj) 
    /* Do something with obj.a */
    if constexpr(condition)
        /* Do something with obj.b */
;

如果 foo 的 constexpr 参数与 print_fun 的参数相同,则实例化此函数并使用它将编译,即

constexpr bool no = false;
foo<no> obj = ;
print_fun<no>(obj);

这确实可以编译,因为错误分支在模板化实体中被丢弃,因此在 print_fun 中使用 obj.b 没有问题。

但是,如果我定义一个类似的 lambda 表达式如下:

template<bool condition>
auto print_lambda = [](foo & obj) 
    /* Do something with obj.a */
    if constexpr(condition)
        /* Do something with obj.b */
;

并实例化它:

constexpr bool no = false;
foo<no> obj = ;
print_lambda<no>(obj);

那么错误的分支不会被丢弃,编译器给我

'b': 不是'foo'的成员

这是预期的行为,是否发生在其他编译器上? 难道我做错了什么? 或者它是编译器中的错误? (Microsoft Visual Studio 版本 15.4.1,gcc 7.2)

查看我使用 gcc 的测试 here,它也不会针对仿函数或函数进行编译。

编辑: 这是我的最小示例的代码,我不知道外部链接是不够的。这可在 Visual Studio 15.4.1 上编译,但注明的行除外。 foo_bar 在我的描述中取代了foo

#include <iostream>

constexpr bool no = false;

struct foo 
    int x;
;

struct bar 
    int y;
;

template <bool, typename AlwaysTy, typename ConditionalTy>
struct Combined : AlwaysTy ;

template <typename AlwaysTy, typename ConditionalTy>
struct Combined<true, AlwaysTy, ConditionalTy> : AlwaysTy, ConditionalTy ;

using foo_bar = Combined<no, foo, bar>;

template<bool condition>
void print_fun(foo_bar & obj) 
    std::cout << obj.x << std::endl;
    if constexpr(condition)
        std::cout << obj.y << std::endl;
;

template<bool condition>
auto print_lambda = [](foo_bar & obj) 
    std::cout << obj.x << std::endl;
    if constexpr(condition)
        std::cout << obj.y << std::endl;
;

int main(int argc, char ** argv) 
    foo_bar obj = ;
    print_lambda<no>(obj); // Does not compile
    print_fun<no>(obj);

【问题讨论】:

"constexpr 用于创建 foo"。 constexprs 不会创建任何东西。您问题的多个部分令人困惑,无法解析。您需要包含一个特定的minimal reproducible example 来演示您所询问的编译错误,以便每个人都可以自己看到它,而不必猜测它是什么。而且您必须将其包含在问题本身中,而不是指向可能随时停止工作的某个外部网站的链接,从而使问题变得毫无意义。由于这个原因,***.com 上的大多数人都会忽略问题中的外部链接。 我已经编辑了我的问题以包含该示例。很抱歉造成混淆,我只是不想详细说明 foo 基于 constexpr 是如何变得不同的,但我希望现在已经清楚了。 【参考方案1】:

根据链接的代码,

template<bool condition>
void print_fun(foo_bar & obj) 
    std::cout << obj.x << std::endl;
    if constexpr(condition)
        std::cout << obj.y << std::endl;

问题在于使用if constexpr,对于模板print_fun 的每个可能的实例化,声明std::cout &lt;&lt; obj.y &lt;&lt; std::endl; 的格式不正确;即无论condition 的值是多少,它总是格式不正确。

注意:对于所有可能的特化,被丢弃的语句不可能是格式错误的:

这种包罗万象的语句的常见解决方法是依赖于类型的表达式,该表达式始终为假:

要修复它,您可以使语句依赖于模板参数,例如

template <bool condition>
using foo_bar = Combined<condition, foo, bar>;

template<bool condition>
void print_fun(foo_bar<condition> & obj) 
    std::cout << obj.x << std::endl;
    if constexpr(condition)
        std::cout << obj.y << std::endl;

并将其用作

foo_bar<no> obj = ;
print_fun<no>(obj);

现在对于obj.yobj 的类型为foo_bar&lt;condition&gt;,这取决于模板参数condition

LIVE

【讨论】:

有趣的是,我现在在实时示例中看到了这一点,但在 Visual Studio 中编译代码时没有区别。函数和仿函数像以前一样工作,而 lambda 仍然发出相同的警告。 @HannoBänsch 我没有 VS,那么消息是什么? (这只是一个警告吗?)顺便说一句,我测试了代码,同时使用 GCC 和 Clang 编译。 问题中提到的相同错误。我理解以前它不应该工作的原因,但现在看来,如果 constexpr 几个月前甚至不支持,VS 更有可能存在错误。 @HannoBänsch 我不确定,但我也猜这是 VS 的错误。 if constepr 或 lambda 可能不完全受支持。 你同意我接受这个答案吗?我坚信我将不得不联系 Microsoft 来解决问题,而您的解决方案显然符合标准并且适用于其他编译器。

以上是关于if constexpr 在模板化 lambda 中未丢弃的错误分支的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 lambda 中使用 constexpr 值而不捕获它吗?

Visual Studio 2015 在 constexpr 中使用 lambda

逗号运算符使lambda表达式非constexpr

使用 lambda 捕获的 constexpr 值作为数组维度

带有 lambda 作为每个实例化的唯一默认参数的模板

将 lambda 作为模板参数传递给函数指针函数模板化