2

I am making a simple class inheriting from std::array. The point is that it should throw a compile time error if the subscript operator is used for an out of bounds index. However, I keep getting an error message. This is the code simplified.

#include <array>

using namespace std;

template<typename type, size_t size>
struct container : array<type,size>
{
    constexpr inline type& operator[](int index) const
    {
        static_assert(index<size,"");
        return ((static_cast<const array<type,size> >(*this))[index]);
    }

    template<class... bracelist>
    constexpr container(bracelist&&... B)
    :array<type,size>{std::forward<bracelist>(B)...}
    {}

    container() = default;
};

int main()
{
    constexpr container<int,4> myarray = {5,6,7,8};
    constexpr int number = myarray[2];
}

The error it gives me is:

main.cpp|80|error: non-constant condition for static assertion
main.cpp|80|error: 'index' is not a constant expression

However, I used "index" in the return statement, and commenting out the static_assert makes it work fine. If index was not a constant expression, wouldn't I not be able to use it in the subscript operator for std::array after the static_cast? I am new to using the constexpr functionality, so any help would be appreciated. Thank you.

Note: I am aware std::array's constexpr subscript operator already does this, I just want to know how to do this for future uses. Thanks.

2
  • That unfortunately does not work. You can do return index >= size ? static_cast<const array<type,size> >(*this)[index] : throw std::runtime_error(""); Commented Aug 15, 2018 at 9:22
  • Are you using C++11 by any chance? Also constexpr inline type & should be constexpr inline type const & and static_cast<const array<type,size> > should be static_cast<const array<type,size> & > and it would be even better to introduce a type alias. Commented Aug 15, 2018 at 9:24

3 Answers 3

2

There are 2 really useful features of constexpr functions, the interplay of which is not always fully appreciated.

  • In constexpr context they only evaluate code paths that are taken for the constexpr arguments.

  • In non-constexpr context they behave exactly like regular functions.

Which means that we can use exceptions to great effect.

Since while in constexpr context, if the exception path is taken, this is a compiler error (throw is not allowed in constexpr context). You get to see the "exception" in your compiler's error output.

example:

#include <array>
#include <stdexcept>

template<typename type, std::size_t size>
struct container : std::array<type,size>
{
    constexpr auto operator[](std::size_t index) const
    -> type const&
    {
        if (index < size)
            return static_cast<const std::array<type,size>>(*this)[index];
        else
            throw std::out_of_range("index out of range" + std::to_string(index));
    }

    template<class... bracelist>
    constexpr container(bracelist&&... B)
    : std::array<type,size>{std::forward<bracelist>(B)...}
    {}

    container() = default;

};

int main()
{
    constexpr container<int,4> myarray = {5,6,7,8};
    constexpr int number = myarray[4];
}

Example output:

main.cpp: In function 'int main()':
main.cpp:28:37:   in 'constexpr' expansion of 'myarray.container<int, 4>::operator[](4)'
main.cpp:13:81: error: expression '<throw-expression>' is not a constant expression
             throw std::out_of_range("index out of range" + std::to_string(index));

This approach is actually more versatile than static_assert, since it works at both compile and runtime.

Sign up to request clarification or add additional context in comments.

2 Comments

You probably want to throw std::out_of_range so that it can be caught as std::exception const&.
@HenriMenke yes, of course. I'll fix that. I was being lazy
1

The thing you have to keep in mind is that constexpr functions can be called at runtime with non constexpr arguments. constexpr means for a function that the function is usable in a compile-time evaluated expression (e.g. another constexpr or a template argument) but not exclusively. A constexpr function can still be called in the classical way, i.e. at run-time with run-time variables. Which means that the parameters of a constexpr function cannot be and are not compile-time constants.

It doesn't apply to your case but in general if you know a parameter will always be called with a compile time constant than you can make it a template parameter.

constexpr void foo(int a)
{
    static_assert(a != 0); // illegal code because the parameter 
                           // cannot be a compile time constant
}

void test()
{
    int x;
    std::cin >> x;
    foo(x); // this is perfectly legal
}
template <int N>
void foo()
{
    static_assert(N != 0); // legal
}

void test()
{
    int x;
    std::cin >> x;
    foo<x>(); // illegal, x is not a compile time constant


    foo<24>(); // legal
    constexpr int c = 11;
    foo<c>();; // legal
}

Comments

0

This is the reason for std::get<N>(array) — it is the only way to assuredly pass a "compile-time value" in a manner that'll satisfy the rules of the language. Your attempt to create a compile-time op[] cannot work. You could of course make your own templated accessor like std::get, but one might ask why not just use std::array as it is already.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.