通过索引对类模板成员的编译时访问
Posted
技术标签:
【中文标题】通过索引对类模板成员的编译时访问【英文标题】:Compile-time access to class template's member by index 【发布时间】:2013-12-21 22:59:51 【问题描述】:给定一个模板,其非类型参数决定了非常量 int
数组成员的大小,我如何在编译时通过整数索引访问数组元素?我希望通过类模板的 getter 方法 at
进行访问。
我想既然类模板必须在运行前实例化,我可以将另一个非类型类模板的enum
成员值传递给前一个类的at
方法,以确保index
参数是编译时常量。
我未定义类模板deliberate_error
,以查看其参数是否在编译时计算并查看错误消息中的编译时结果。
template <unsigned int N>
struct compile_time_int
enum num = N;
;
template <unsigned int N>
struct array_wrapper
int arr[N];
template <unsigned int Ind>
constexpr int const& at(compile_time_int<Ind> const& index) const
return arr[index.num];
;
template <unsigned int> struct deliberate_error;
int main()
compile_time_int<2> cti;
array_wrapper<3> aw;
aw.at(cti);
deliberate_error<cti.num> my_error1;
deliberate_error<aw.at(cti)> my_error2;
aw.at(cti);
没有报错,所以我想如果我将相同的表达式传递给deliberate_error
实例my_error2
,编译器会在错误消息中显示arr[2]
的值。
my_error1
导致 g++ error: aggregate 'deliberate_error<2u> my_error1' has incomplete type and cannot be defined
,
显示cti
的包装整数值2
。所以,我想如果我将相同的cti
传递给对象aw
的getter,然后将结果传递给my_error2
,我可以在错误消息中得到arr[2]
。但相反,它会打印:
error: the value of 'aw' is not usable in a constant expression
note: 'aw' was not declared 'constexpr'
note: in template argument for type 'unsigned int'
error: invalid type in declaration before ';'
所以,我尝试在 aw
的声明之前添加 constexpr
,但这会产生更多不受欢迎的错误。这里出了什么问题,我该如何解决?
【问题讨论】:
“所以,我尝试在 aw 的声明之前添加 constexpr,但这会产生更多不希望的错误。” 这听起来是正确的做法,另请参阅 remyabel 的回答。你得到什么错误? (注意:你需要初始化cti
,即使它只是使用
,你需要做同样的事情,constexpr
+ 初始化器,用于aw
。)
compile_time_int<N>
似乎是对 std::integral_constant<int,N>
的改造
@remyabel 我认为你在正确的轨道上,aw
和 cti
都需要用 constexpr
声明并初始化。
@DyP,这些是错误:coliru.stacked-crooked.com/a/b314af2d534f7390
【参考方案1】:
(请注意,据我所知,std::array
和 std::get
已经解决了您的问题。)
主要问题是您需要将您的实例 aw
设为 constexpr
,当然您需要使用一些值对其进行初始化:
constexpr array_wrapper<3> aw = 1, 2, 3 ;
关于函数at
,你可以把它写成一个普通函数,但只需将它指定为constexpr
:
constexpr int const& at(int i) const
return arr[i];
那么,aw.at(0)
可以用作常量表达式:Live Demo
这样做的好处是您可以在编译时和运行时表达式中使用此函数,分别具有静态和动态索引。
如果你真的希望它被模板化,你可以把它写成像std::get<N>
这样的非成员或类成员,但使用int
类型的模板参数(或size_t
或类似) .这简化了它的定义(并且你可以摆脱你的 compile_time_int
类模板):
template<int Index>
constexpr int const& at() const
return arr[Index];
那么,aw.at<0>()
可以用作常量表达式:Live Demo
第二种方法的好处是保证了索引是静态的,所以我们可以在函数中使用它进行静态边界检查,不会增加任何性能损失。不知道第一个版本是否可行。
【讨论】:
对于 POD 类型,编译器实现了一个 c'tor,按照 AFAIK 的顺序获取成员。所以在这种情况下是int[3]
。而 1, 2, 3
可以用作int[3]
的初始化器。这可能只适用于 C++11。 -- 必须初始化它的原因是如果添加 constexpr,编译器需要在编译时知道该值。对于 POD 类型,这意味着您必须使用成员对其进行初始化,如上所述。如果您编写了自己的默认 c'tor(没有 args),那么您可以编写 constexpr array_wrapper<3> aw;
但这个 c'tor 也必须是 constexpr。
话虽如此,除了 constexpr,如果你根本不初始化 POD 类型,它们就有未初始化的内容。您可以稍后为其分配值,因此内容定义明确。但是constexpr
必须立即初始化;编译器不会分析您的程序以查看它是否稍后会被分配。请记住: constexpr 在编译时进行评估;当使用 C++11 将此功能添加到 C++ 时,控制流被有意设计得非常有限。
std:array
是不够的,因为它的 operator[]
在 C++11 中不需要是 constexpr
,仅在 C++14 中。
@Casey 使用std::get
它可以工作,但是模板语法而不是(在我看来)更好的函数参数语法。
@CodeBricks 垃圾内容取决于运行时的内存内容。所以它在编译时是未知的,所以 C++ constexpr 特性需要初始化。其他一切都没有任何意义。为什么要让编译过程依赖垃圾?【参考方案2】:
也许就是这样:
template <unsigned int N>
struct array_wrapper
int arr[N];
;
template <unsigned int I, unsigned int N>
constexpr int & at(array_wrapper<N> & a)
static_assert(I < N, "static array index out of bounds");
return a.arr[I];
// add a "const" overload, too
用法:
array_wrapper<10> x;
at<3>(x) = 42;
【讨论】:
现在看起来很像std::array
与 std::get
的组合。我认为您可以将 at
设为 constexpr
函数。
@DyP:好点。是的,我的观点是试图强调部分 OP 的大量车轮改造......
@KerrekSB,+1。谢谢。您的回答很优雅,但被接受的回答明确显示了我的代码出错的地方。以上是关于通过索引对类模板成员的编译时访问的主要内容,如果未能解决你的问题,请参考以下文章
angular 2 离线模板编译出错 - @angular/http 没有导出的成员 HTTP_PROVIDERS