【问题标题】:When are enable_if_t expressions evaluated?何时评估 enable_if_t 表达式?
【发布时间】:2015-06-30 23:11:28
【问题描述】:

这是我的代码:

template<class T, std::enable_if_t<T::value == 1> * = nullptr>
void foo(T) {
    std::cout<<"test"<<std::endl;
} // #1

template<class T, std::enable_if_t<T::value != 1> * = nullptr>
void foo(T) {} // #2

class test{
  public:
    constexpr static int value = 1;

    test() {}
};


int main() {
    test p;
    foo(p);
}

由于std::enable_if_t&lt;T::value != 1&gt; 要求我的value 是静态的并且constexpr,我假设它是在编译时评估的(我需要确认)。但是由于它是一个模板,它将取决于T,但我的主目录上也有这个,它们不是constexpr:

int main() {
    test p;
    foo(p);
}

输出:

test

那么此时如何评估事情(包括函数在内的初始化顺序)?因为编译器需要决定创建哪个 foo 版本。

【问题讨论】:

  • 好问题。从未尝试过。

标签: c++


【解决方案1】:

一切都在编译时进行评估。当您调用foo(p) 时,编译器将进行名称查找以查找foo 和p 是什么。它会为foo 找到两个名称:

template<class T, std::enable_if_t<T::value == 1>* = nullptr>
void foo(T);

template<class T, std::enable_if_t<T::value != 1>* = nullptr>
void foo(T);

然后它将尝试执行模板替换。请注意,模板替换失败不是错误 (sfinae)。 T 被推导出为test,所以我们必须非类型模板参数。第一个类型为std::enable_if_t&lt;T::value == 1&gt;*。此时我们必须评估test::value 是什么。为了使替换成功,必须有一些名为value 的常量等于一。如果没有任何名称为value,或者它是一个类型,或者一个成员变量,或者不等于1,替换将失败。在这种情况下,有一个static constexprvalue是1,所以它成功了。

第二个重载模板推导失败,因为没有enable_if&lt;false&gt;::type。

由于只有一个可行的重载,它是最好的可行重载,我们选择它。所有这些都是在编译时完成的。

请注意,如果你有类似的东西:

struct bad_test {
    int value = 1;
};

foo(bad_test{});

这将无法编译,并出现一个错误,指出没有与 foo 匹配的函数 - 您的两个重载模板替换都将失败,因为无法在该上下文中评估 T::value。

【讨论】:

  • 简而言之:main 在函数生成之前首先被评估?
  • @Carlo Brew:这取决于您所说的“第一”是什么意思。函数模板不是函数。它们是模板。编译器首先分析它们并记住它们(作为模板)。稍后,当您在main 中执行foo(p) 时,编译器会找到合适的模板并从中生成实际的函数foo。
  • @CarloBrew 您正在寻找的词是函数在被调用时被实例化。评估顺序是关于 runtime 的哪些表达式先于其他表达式发生。
  • @Barry 所以只是一个后续问题,真的需要constexpr static int value = 1; 吗?我一直在尝试它是int value = 1; 还是static int value = 1;,但它不会让我编译。你能解释一下为什么模板参数要求它是 constexpr 和静态的吗?
  • @CarloBrew 因为模板实例化发生在编译时——所以你需要实例化模板的一切(在这种情况下,知道value 是否为1)在那个时候必须可用。标准术语是:它必须是core constant expression。
【解决方案2】:

模板参数始终是在编译时严格评估的编译时实体。

在您使用foo(p) 的示例中,模板参数推导出为T == test,这是在编译时完成的。一旦知道T == test,T::value 的值就称为test::value,它存在并且是一个constexpr(在编译时也知道)。

【讨论】:

  • 但在我的例子中,它是T。那么它是如何知道我即将通过 test p 的,其中唯一的 constexpr 是 value 测试成员?
  • 在这种情况下“即将通过”是什么意思?编译器清楚地看到您通过 p,它是test 类型的对象。此时它会推断出T == test 并从您的模板中实例化适当版本的foo。模板函数通常实例化(即变成真正的函数)在调用点,而不是更早。
  • 啊,我明白了。所以函数模板只有被调用才会生成函数?
  • @Carlo Brew:是的,完全正确。实际函数是在调用点“按需”生成(实例化)的,所有模板参数都是已知的。为模板参数的每个组合生成一个单独的函数版本。可以在不调用的情况下强制模板实例化,但这是另一回事。
猜你喜欢
  • 2022-10-15
  • 2014-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多