I have this CRTP
base class:
template <class Child>
class Container {
friend Child;
Container() {};
public:
decltype(auto) begin() { return static_cast<const Child&>(*this).abegin(); }
decltype(auto) end() { return static_cast<Child&>(*this).aend(); }
decltype(auto) begin() const { return static_cast<const Child&>(*this).abegin(); }
decltype(auto) end() const { return static_cast<const Child&>(*this).aend(); }
};
And now, an array, trying to be the Child of that container:
template<typename T, size_t N>
class Array: public Container<Array<T, N>> {
public:
T array[N];
public:
using iterator = zero::iterator::input_iter<T>;
using const_iterator = zero::iterator::input_iter<const T>;
// Iterator stuff
iterator abegin() { return iterator(&array[0]); }
iterator aend() { return iterator(&array[N]); }
constexpr const_iterator abegin() const { return const_iterator(&array[0]); }
constexpr const_iterator aend() const { return const_iterator(&array[N]); }
/**
* @brief returns the number of elements stored in the underlying array
*/
[[nodiscard]]
inline consteval int size() const noexcept { return N; }
template <typename... InitValues>
Array(InitValues... init_values)
: array{ init_values... } {}
// code goes on...
Here it's the usage:
import std;
import zero;
import collections;
import iterator;
import container;
import type_info;
using namespace zero;
int main() {
constexpr collections::Array a = collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L};
Container b = collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L};
std::cout << "Iterating over the values of a constexpr zero::collection!" << std::endl;
std::cout << "decltype a: " << zero::types::type_name<decltype(a)>() << std::endl;
for (long value : a)
std::cout << " - [constexpr] Value: " << value << std::endl;
std::cout << "Iterating over the values of a zero::container!" << std::endl;
std::cout << "decltype a: " << zero::types::type_name<decltype(b)>() << std::endl;
for (long value : b)
std::cout << " - Value: " << value << std::endl;
return 0;
}
And the results:
Iterating over the values of a constexpr zero::collection!
decltype a: const zero::collections::Array<long, 5>
- [constexpr] Value: 1
- [constexpr] Value: 2
- [constexpr] Value: 3
- [constexpr] Value: 4
- [constexpr] Value: 5
Iterating over the values of a zero::container!
decltype a: zero::Container<zero::collections::Array<long, 5>>
- Value: 8
- Value: 0
- Value: 1
- Value: 2
- Value: 3
Well, I just got UB!
I am missunderstading things, pretty sure, but I am not able to understand what I am writting and doing bad.
- I want to be able to use
Container
as a interface type in type definitions, functions parameters... typical places without dynamic polimorphism, just usingCRTP
.
Is someone able to spot my mistakes and help me to fix them?
EDIT: As asked here is the live example on Godbolt
CodePudding user response:
The immediate reason of the UB is object slicing. The type of b
is Container<Array<long, 5>>
— and it is a complete object. There is no Array<long, 5>
around to be a base class subobject of. The temporary collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L}
isn't one. So every cast inside member functions of this particular container is UB.
In order to fix it, you need b
to be a reference to a Container
. Let's try:
const Container& b = collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L};
Oops! This doesn't compile. The reason why you was able to write just Container
is CTAD, and CTAD doesn't work for references or pointers or anything but the class template itself. Container
is not a type and cannot be used as such. It is a template. So not having CTAD on your side, you need this:
const Container<collections::Array<long, 5>> & b =
collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L};
(const
because it's a reference to a temporary)
Now this doesn't look as attractive as just Container
, does it? And if you need to spell out collections::Array<long, 5>
, you could just as well write
const collections::Array<long, 5> & b =
collections::Array<long, 5>{1L, 2L, 3L, 4L, 5L};
which is shorter and contains just as much information about b
.
Now why do you need Container
again?