Search code examples
c++c++11runtimecompile-timestatic-assert

static_assert on indices know at compile time


Is there a way to statically assert on indices known at compile-time and run-time assert otherwise? Example:

template <class T, int Dim>
class Foo
{
    T _data[Dim];
    public:
        const T &operator[](int idx) const
        {
            static_assert(idx < Dim, "out of range"); // error C2131: expression did not evaluate to a constant
            return _data[idx];
        }
};

int main()
{
    Foo<float, 2> foo;

    foo[0];
    foo[1];
    foo[2]; // compiler error

    for (int i=0; i<5; ++i)
    {
        foo[i]; // run time assert when i > 1
    }

    return 0;
}

Solution

  • I don't think it's possible obtain what do you want with a single function.

    Even if you develop a constexpr function, I don't think your are able to detect when is executed run-time and when executed compile time and act in a different way.

    But you can develop different functions.

    By example, a template get<>(), where the template argument is the index, that can be used only with an index known at compile-time and that can perform a static_assert(), and an at(std::size_t), that can receive an index computed at run time with a run time check.

    En passant:

    1) I suggest, as usual in STL, the use of at() for a bound checked access and an operator[]() for a bound unchecked access

    2) and I suggest the use of an unsigned index or you have to check that the index is >= 0.

    The following is a working example

    #include <iostream>
    #include <stdexcept>
    
    template <class T, std::size_t Dim>
    class Foo
     {
       private:
          T _data[Dim];
    
       public:
          T const & operator[] (std::size_t idx) const
           { return _data[idx]; }
    
          template <std::size_t IDX>
          T const & get () const
           {
             static_assert(IDX < Dim, "out of range");
    
             return this->operator[](IDX);
           }
    
          T const & at (std::size_t idx) const
           {
             if ( idx >= Dim )
                throw std::range_error("out of range");
    
             return this->operator[](idx);
           }
     };
    
    int main ()
     {
       Foo<float, 2U> foo;
    
       foo.get<0U>();
       foo.get<1U>();
       //foo.get<2U>(); // compiler error
    
       for ( auto i = 0U ; i < 5U ; ++i )
          foo.at(i); // run time exception when i > 1
    
       return 0;
     }