I am trying to define a C 20 concept
that enforces the implementors to check for an out of bounds.
template <size_t Num>
concept IsInBounds = requires (const size_t idx) {
requires idx >= Num;
};
So, for a method that I have in a custom container that returns a non-const reference to an element inside it, I want to check with the concept if the index required is out-of-bounds.
auto mut_ref_at(const size_t idx) const -> T(&) requires IsInBounds<N>
{
return (T&) array[idx];
}
That's obviously doesn't works:
error: substitution into constraint expression resulted in a non-constant expression
requires idx >= Num;
function parameter 'idx' with unknown value cannot be used in a constant expression
requires idx >= Num;
So, if I understand something about concepts, the concept is understanding that the requires (const size_t idx)
clause, where I have a parameter idx, is substituting the parameter idx
of my method?
And, exists some way with the concept of constraint the value idx
of the mut_ref_at
method to the size of the T array[N]
member?
Full class declaration:
template <typename T, zero::size_t N>
class StackArray{
private:
T array[N];
public:
template <typename... InitValues>
StackArray(InitValues... init_values)
: array{ init_values... } {}
/**
* @brief delete the `new` operator, since the intended usage of
* the type is to be a wrapper over a C-style array.
*
* @return void*
*/
void* operator new(std::size_t) = delete;
/**
* @brief Returns a mut reference to the element at specified location `idx`,
* with bounds checking.
*
* @param idx a `size_t` value for specifiying the position of
* the element to retrieve.
* @return T& to the element at idx position
*/
constexpr auto mut_ref_at(const size_t idx) const -> T(&) requires IsInBounds<N>
{
return (T&) array[idx];
}
Edit: changed concept declaration
CodePudding user response:
If you want to provide the out-of-bounds
checking at compile time, you must provide the index via template parameter. You should rely on a std::size_t
value to provide the index that the client code wants to retrieve, and, with the concept, index will be determined when the template is instanciated. This will lead the compiler to refuse to compile if the index is out-of-bounds
.
Your code will looks like:
template <size_t I, size_t N>
concept IsInBounds = requires () {
requires I <= N;
};
You use your class template parameter N
to determine the array capacity. So, you can check I against N. If I is greater than N, concept will fail, and code will not compile.
And your method will looks like:
template <size_t I>
constexpr T& mut_ref_at() requires IsInBounds<I, N>
{
return arr[I];
}
Note that, as others pointed out, cast to (T&)
it's ugly and unnecessary. Trailing return is also verbose, the return type it's clearly always T&
.