通过索引对类模板成员的编译时访问

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&lt;2u&gt; 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&lt;N&gt; 似乎是对 std::integral_constant&lt;int,N&gt; 的改造 @remyabel 我认为你在正确的轨道上,awcti 都需要用 constexpr 声明并初始化。 @DyP,这些是错误:coliru.stacked-crooked.com/a/b314af2d534f7390 【参考方案1】:

(请注意,据我所知,std::arraystd::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&lt;N&gt;这样的非成员或类成员,但使用int类型的模板参数(或size_t或类似) .这简化了它的定义(并且你可以摆脱你的 compile_time_int 类模板):

template<int Index>
constexpr int const& at() const 
    return arr[Index];

那么,aw.at&lt;0&gt;() 可以用作常量表达式:Live Demo

第二种方法的好处是保证了索引是静态的,所以我们可以在函数中使用它进行静态边界检查,不会增加任何性能损失。不知道第一个版本是否可行。

【讨论】:

对于 POD 类型,编译器实现了一个 c'tor,按照 AFAIK 的顺序获取成员。所以在这种情况下是int[3]。而 1, 2, 3 可以用作int[3] 的初始化器。这可能只适用于 C++11。 -- 必须初始化它的原因是如果添加 constexpr,编译器需要在编译时知道该值。对于 POD 类型,这意味着您必须使用成员对其进行初始化,如上所述。如果您编写了自己的默认 c'tor(没有 args),那么您可以编写 constexpr array_wrapper&lt;3&gt; 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::arraystd::get 的组合。我认为您可以将 at 设为 constexpr 函数。 @DyP:好点。是的,我的观点是试图强调部分 OP 的大量车轮改造...... @KerrekSB,+1。谢谢。您的回答很优雅,但被接受的回答明确显示了我的代码出错的地方。

以上是关于通过索引对类模板成员的编译时访问的主要内容,如果未能解决你的问题,请参考以下文章

inline 关键字对类成员模板函数的影响?

angular 2 离线模板编译出错 - @angular/http 没有导出的成员 HTTP_PROVIDERS

编译时:计算类型 X 的成员的 #(~N) 个并定义一个成员数组 [N]?

C ++编译时检查模板类型中是否存在方法

java 多态

this指针