1

Given a template whose non-type parameter determines the size of a non-const int array member, how can I access the array elements by an integral index at compile time? I want the access to be done via the class template’s getter method at.

I figured since class templates must be instantiated before runtime, I can pass another non-type class template’s enum member value to the prior class’s at method to ensure the index argument is a compile-time constant.

I left the class template deliberate_error undefined to see if its arguments are computed at compile time and to view the compile-time results in the error messages.

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); doesn’t give an error, so I thought that if I passed the same expression to deliberate_error instance my_error2, the compiler will display the value of arr[2] in the error message.

my_error1 causes g++ error: aggregate 'deliberate_error<2u> my_error1' has incomplete type and cannot be defined,

showing cti’s wrapped integral value 2. So, I thought if I passed the same cti to object aw's getter, and then pass the result to my_error2, I can get arr[2] in the error message. But instead, it prints:

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 ';'

So, I tried prepending constexpr to aw’s declaration, but that gives even more undesirable errors. What’s wrong here, and how can I fix it?

CodeBricks
  • 1,771
  • 3
  • 17
  • 37
  • 1
    *"So, I tried prepending constexpr to aw’s declaration, but that gives even more undesirable errors."* That sounds like the right thing to do, also see remyabel's anwer. What errors do you get? (Note: you'll need to initialize `cti`, even if it's just with `{}`, and you need to do the same thing, `constexpr` + initializer, for `aw`.) – dyp Dec 21 '13 at 23:20
  • 1
    `compile_time_int` seems like a reinvention of [`std::integral_constant`](http://en.cppreference.com/w/cpp/types/integral_constant) – leemes Dec 21 '13 at 23:20
  • 1
    @remyabel I think you're on the right track, both `aw` and `cti` need to be declared with `constexpr` and initialized. – dyp Dec 21 '13 at 23:25
  • @DyP, these were the errors: http://coliru.stacked-crooked.com/a/b314af2d534f7390 – CodeBricks Dec 21 '13 at 23:57

2 Answers2

2

Maybe just this:

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

Usage:

array_wrapper<10> x;
at<3>(x) = 42;
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • This now looks pretty much like `std::array` combined with [`std::get`](http://en.cppreference.com/w/cpp/container/array/get). I think you could make that `at` a `constexpr` function. – dyp Dec 21 '13 at 23:14
  • @DyP: Good point. Yes, it was sort of my point trying to highlight the large amount of wheel reinvention on part of the OP... – Kerrek SB Dec 21 '13 at 23:16
  • @KerrekSB, +1. Thanks. Your answer is elegant, but the accepted one showed specifically where my code went wrong. – CodeBricks Dec 22 '13 at 01:30
2

(Note that as far as I see, std::array with std::get already solves your problem.)


The main issue is that you need your instance aw to be constexpr and of course you need to initialize it with some values:

constexpr array_wrapper<3> aw = { 1, 2, 3 };

Regarding the function at, you can write it as a normal function but simply specify it as constexpr:

constexpr int const& at(int i) const {
    return arr[i];
}

Then, aw.at(0) can be used as a constant expression: Live Demo

The advantage of this is that you can use this function in both compile-time and runtime expressions, with static and dynamic indexing, respectively.


If you really want it to be templated, you can either write it as a non-member like std::get<N> or as a class member, but use a template parameter of type int (or size_t or similar). That simplifies its definition (and you can get rid of your compile_time_int class template):

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

Then, aw.at<0>() can be used as a constant expression: Live Demo

The advantage of the second method is that the index is guaranteed to be static, so we can use it in the function for static bound checking, which will not add any performance penalty. I don't know if this is possible with the first version.

leemes
  • 44,967
  • 21
  • 135
  • 183
  • Why does `aw` need to be initialized if it has a default ctor? How did you initialize `aw` with a `braced-init-list` of 3 values if its only ctor is the compiler-provided one? – CodeBricks Dec 21 '13 at 23:53
  • 1
    For POD types, the compiler implements a c'tor taking the members in their order AFAIK. So it's an `int[3]` in this case. And `{ 1, 2, 3 }` can be used as an initializer for `int[3]`. It might be possible that this only works since C++11. -- The reason why it has to be initialized is if you add constexpr, the compiler needs to know the value during compile-time. For POD types this means you have to initialize it with the members, as explained above. If you wrote your own default c'tor (without args) then you can write `constexpr array_wrapper<3> aw;` but this c'tor has to be constexpr too. – leemes Dec 21 '13 at 23:59
  • 1
    That being said, constexpr aside, if you don't initialize POD types at all, they have uninitialized content. You can assign values to it later on, so the content is well defined. But `constexpr` has to be initialized right away; the compiler will not analyze your program to see if it will be assigned later. Remember: constexpr is evaluated at compile-time; the control flow is intentionally designed to be very limited when this functionality was added to C++ with C++11. – leemes Dec 22 '13 at 00:01
  • I thought the compiler initializes POD `array[]`s that the programmer didn’t explicitly initialize. Wouldn’t that allow the compiler to know the garbage values it initialized the array with? – CodeBricks Dec 22 '13 at 00:04
  • 1
    `std:array` is insufficient since its `operator[]` is not required to be `constexpr` in C++11, only in C++14. – Casey Dec 22 '13 at 00:15
  • 1
    @Casey With [`std::get`](http://en.cppreference.com/w/cpp/container/array/get) it works, but the template syntax instead of the (in my opinion) nicer function parameter syntax. – leemes Dec 22 '13 at 00:32
  • 1
    @CodeBricks The garbage content depends on the memory content at runtime. So it's not known at compile-time, so the C++ constexpr feature requires an initialization. Everything else doesn't make any sense. Why would you want the compilation process to be dependent on garbage? – leemes Dec 22 '13 at 00:34
  • 1
    The problem of `constexpr a_w<3> aw;` is the same as for `const a_w<3> aw;`: variables declared `const` require an initialization. Just writing `some_type name;` invokes default-initialization. For class types, this invokes the default-constructor. A "compiler-provided" default-constructor recursively default-initializes the bases and members. Default-init of built-in types and arrays thereof means **no** init. Therefore, the default-ctor might not initialize anything. `constexpr` strictly enforces init, `const` only requires a user-provided default-ctor (to make a non-init case explicit). – dyp Dec 22 '13 at 01:08
  • 1
    Btw: `array_wrapper<3> aw = { 1, 2, 3 };` is just plain-old aggregate-initialization, as `aw` has no non-defaulted constructors. Aggregate-initialization is already available in C++98 and C. – dyp Dec 22 '13 at 01:10