模板参数推导如何区分左值和字面量/编译时值

How does template argument deduction distinguish between an lvalue and a literal/compile-time value

本文关键字:编译 参数 何区      更新时间:2023-10-16

这是一个关于OP对constexpr对重载有用的解决方案的问题。

基本上,他用了

template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, int>::type
f(T&& n) { ... }

template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value, int>::type
f(T&& n) { ... }

知道调用f()时是否使用一个编译时变量(如:literal: f(42))或一个左值(如:局部变量:f(argc))作为参数。

(我预计,在两个调用中,第一个重载将被调用(即std::is_arithmetic<T>::value == true)

下面是一个完整的例子:

Run It Online

#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
template<class T>
constexpr
typename std::enable_if<std::is_arithmetic<T>::value,
                        int>::type
inline f(T&& n)
{
    //cout << "compile time" << endl;
    return 1;
}
template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value,
                        int>::type
inline f(T&& n)
{
    //cout << "run time" << endl;
    return 0;
}
int main(int argc, char* argv[])
{
    const     int rt = f(argc);
    constexpr int ct = f(42);
    cout << "rt: " << rt << endl;
    cout << "ct: " << ct << endl;
}

表单

的模板函数
template <typename T>
void func(T&& t);

看起来好像接受了一个r值引用。但实际上T&&这里是Scott Meyers所说的通用引用,或者称为转发引用。根据参数的值类别不同会发生不同的事情。让我们看一下每一种情况:

  1. t是非const左值,例如

    int i = 0;
    func(i);
    

    在本例中,T被推导为对int的左值引用,即T=int&

  2. t是const左值,例如

    const int i = 1;
    func(i);
    

    同样,在这种情况下,T被推断为const int&

  3. t为右值,例如

    func(1);
    

    在这种情况下,T被推断为int,正如我们可能期望的那样

为什么这些演绎会以这种方式发生,与引用坍缩的规则有关;如果你感兴趣的话,我强烈推荐你阅读Scott Meyers关于这个主题的文章。

上面的最后一个例子也说明了在C和c++中,字面值(字符串字面值除外)总是右值。

这和enable_if有什么关系?如果你的f是用一个整数字面值调用的,那么T就会被推断为普通的int。显然,is_arithmetic<int>为真,因此第二个函数得到SFINAE输出,并调用第一个函数。

但是,当带左值调用时,T被推导为(const) int&。引用是而不是算术,因此第一个函数消失,只剩下第二个函数要调用。