为什么非 constexpr std::integral_constant 用作模板参数?
Why does a non-constexpr std::integral_constant work as a template argument?
我的问题是为什么以下代码是有效的 C++:
#include <iostream>
#include <tuple>
#include <type_traits>
std::tuple<const char *, const char *> tuple("Hello", "world");
std::integral_constant<std::size_t, 0> zero;
std::integral_constant<std::size_t, 1> one;
template<typename T> const char *
lookup(T n)
{
// I would expect to have to write this:
// return std::get<decltype(n)::value>(tuple);
// But actually this works:
return std::get<n>(tuple);
}
int
main()
{
std::cout << lookup(zero) << " " << lookup(one) << std::endl;
}
当然,我很高兴能够以这种方式编程。此外,我知道 std::integral_constant
有一个 constexpr
转换运算符。但是,参数 n
到 lookup
不是 constexpr,所以我很困惑非 constexpr 对象上的非静态方法(即使方法本身是 constexpr)如何可能 return 一个编译时常量。
当然,我们碰巧知道在这种情况下转换运算符的主体不查看运行时值,但类型签名中没有任何内容可以保证这一点。例如,以下类型显然不起作用,即使它也有一个 constexpr 转换运算符:
struct bad_const {
const std::size_t value;
constexpr bad_const(std::size_t v) : value(v) {}
constexpr operator std::size_t() const noexcept { return value; }
};
bad_const badone(1);
如果忽略隐式 this
参数,是否有一些额外的 属性 方法被视为不同?
在 std::get<n>()
中,n
需要是 size_t
,但您传递了 std::integral_constant<...>
。所以编译器检查是否存在从一个到另一个的隐式转换,它试图调用 std::integral_constant<...>::operator size_t()
。碰巧这正是您的 std::integral_constant<std::size_t, 0> zero;
拥有的运算符。所以一切都很好地打字。
但是你问为什么这是一个compile-time常量:operator size_t()
returns static constexpr T value = v;
。所以 integral_constant
本身是否为 const 并不重要。它对返回的常量没有影响。
Of course, we happen to know in this case that the body of the conversion operator doesn't look at the runtime value, but nothing in the type signature guarantees that.
正确。你错过的是这无关紧要的事实。
在常量表达式计算期间调用函数时,编译器会检查函数是否为 constexpr
。如果不是 constexpr
,则封闭求值无法生成常量表达式。
但是如果它是constexpr
,那么编译器会在编译时执行它的主体,并检查它的主体中是否有任何东西使封闭的评估不符合常量表达式的资格.
所以在 std::integral_constant
的转换运算符的情况下,编译器在编译时执行它的主体,看到它只是 returns 模板参数的值,这很好。
如果编译器正在评估某个其他类型的 constexpr
成员函数,它读取对象的非 const
non-static 数据成员(并且对象不是 constexpr
),那么此时编译器会确定封闭的求值不是常量表达式。
有点令人沮丧的是,当您看到一个已声明的函数 constexpr
时,这并没有告诉您关于该函数的 哪个 求值产生常量表达式的任何信息,但是就是这样。
我的问题是为什么以下代码是有效的 C++:
#include <iostream>
#include <tuple>
#include <type_traits>
std::tuple<const char *, const char *> tuple("Hello", "world");
std::integral_constant<std::size_t, 0> zero;
std::integral_constant<std::size_t, 1> one;
template<typename T> const char *
lookup(T n)
{
// I would expect to have to write this:
// return std::get<decltype(n)::value>(tuple);
// But actually this works:
return std::get<n>(tuple);
}
int
main()
{
std::cout << lookup(zero) << " " << lookup(one) << std::endl;
}
当然,我很高兴能够以这种方式编程。此外,我知道 std::integral_constant
有一个 constexpr
转换运算符。但是,参数 n
到 lookup
不是 constexpr,所以我很困惑非 constexpr 对象上的非静态方法(即使方法本身是 constexpr)如何可能 return 一个编译时常量。
当然,我们碰巧知道在这种情况下转换运算符的主体不查看运行时值,但类型签名中没有任何内容可以保证这一点。例如,以下类型显然不起作用,即使它也有一个 constexpr 转换运算符:
struct bad_const {
const std::size_t value;
constexpr bad_const(std::size_t v) : value(v) {}
constexpr operator std::size_t() const noexcept { return value; }
};
bad_const badone(1);
如果忽略隐式 this
参数,是否有一些额外的 属性 方法被视为不同?
在 std::get<n>()
中,n
需要是 size_t
,但您传递了 std::integral_constant<...>
。所以编译器检查是否存在从一个到另一个的隐式转换,它试图调用 std::integral_constant<...>::operator size_t()
。碰巧这正是您的 std::integral_constant<std::size_t, 0> zero;
拥有的运算符。所以一切都很好地打字。
但是你问为什么这是一个compile-time常量:operator size_t()
returns static constexpr T value = v;
。所以 integral_constant
本身是否为 const 并不重要。它对返回的常量没有影响。
Of course, we happen to know in this case that the body of the conversion operator doesn't look at the runtime value, but nothing in the type signature guarantees that.
正确。你错过的是这无关紧要的事实。
在常量表达式计算期间调用函数时,编译器会检查函数是否为 constexpr
。如果不是 constexpr
,则封闭求值无法生成常量表达式。
但是如果它是constexpr
,那么编译器会在编译时执行它的主体,并检查它的主体中是否有任何东西使封闭的评估不符合常量表达式的资格.
所以在 std::integral_constant
的转换运算符的情况下,编译器在编译时执行它的主体,看到它只是 returns 模板参数的值,这很好。
如果编译器正在评估某个其他类型的 constexpr
成员函数,它读取对象的非 const
non-static 数据成员(并且对象不是 constexpr
),那么此时编译器会确定封闭的求值不是常量表达式。
有点令人沮丧的是,当您看到一个已声明的函数 constexpr
时,这并没有告诉您关于该函数的 哪个 求值产生常量表达式的任何信息,但是就是这样。