Reputation: 4938
I'd like to have a static polymorphism with the parent class having a template std::array
size. This code works fine:
#include <iostream>
#include <array>
using namespace std;
template <size_t size>
class Message
{
public:
size_t GetSize() { return size; }
private:
std::array<uint8_t, size> data_{};
};
class Command : public Message<12>
{
public:
static const size_t kCmdSize{12};
private:
};
class Reply : public Message<16>
{
public:
static const size_t kCmdSize{12};
private:
};
int main()
{
Command cmd{};
Reply rpl{};
cout << "Size: " << cmd.GetSize() << "|" << rpl.GetSize() << endl;
return 0;
}
But I'm not a huge fan of magic numbers.
Is there any way to use a constant declared in the child class as the parameter to the parent class? Something like that:
class Command : public Message<kCmdSize>
{
public:
static const size_t kCmdSize{12};
private:
};
Using this directly attempts to use a variable from a class that doesn't exist yet. Using C++14.
Upvotes: 0
Views: 84
Reputation: 14589
That's common problem and there is solution used widely, e.g. in implementations of stream components of C++ library. The type definitions and constants related to concrete derived class became part of specialization for a trait class:
template <class T>
struct CommandTrait;
template <class T>
struct Message : public CommandTrait<T>
{
constexpr size_t GetSize() { return this->kCmdSize; } // or Message::kCmdSize, the same in this case
std::array<std::uint8_t, Message::size> data_{};
};
template <size_t _Sz, size_t _CSc = 12 >
struct MessageSize {
static constexpr std::size_t size = _Sz;
static constexpr size_t kCmdSize{ _CSc };
};
template <>
struct CommandTrait<struct Command> : MessageSize<12> {};
template <>
struct CommandTrait<class Reply> : MessageSize<16> {};
class Command : public Message<Command>
{
};
class Reply : public Message<Reply>
{
};
Note that this->
is important to suggest compiler that "kCmdSize" is a name depending on template's parameter. You need a template-dependant name (i.e. qualified for static use or using this-> for runtime). That tells compiler to expect that such name exist or will be existing in future, at time of instantiation. SHould not forget about possibility of using constexpr:
template <class T>
struct Message : public CommandTrait<T>
{
static constexpr size_t GetSize() { return Message::kCmdSize; }
std::array<std::uint8_t, GetSize()> data_{};
};
Trait class may have a common base, but generally type erasure is not required where such structs are used.
Upvotes: 1
Reputation: 1669
How about something like this? Is there a concern to pass the number from your main-instance instead of letting it hang open in the class impl.
#include <iostream>
#include <array>
using namespace std;
template <size_t size>
class Message
{
public:
size_t GetSize() { return size; }
private:
std::array<uint8_t, size> data_{};
};
template<size_t N>
class Command : public Message<N>
{
public:
static const size_t kCmdSize{N};
private:
};
int main()
{
Command<12> cmd{};
cout << "Size: " << cmd.GetSize() << endl;
return 0;
}
Upvotes: 0
Reputation: 11506
If you're fine with one extra layer of indirection, then you can use a "traits class" kind of solution:
#include <array>
#include <cstdint>
template <class T> struct MessageSize;
template <std::size_t size>
class Message {
public:
std::size_t GetSize() { return size; }
private:
std::array<std::uint8_t, size> data_{};
};
// Forward declare class for upcoming specialization.
class Command;
// Specialize message size for Command class.
template <> struct MessageSize<Command> {
static constexpr std::size_t size = 12;
};
class Command : public Message<MessageSize<Command>::size> { };
Note that in my example the Message
class itself does not use the MessageSize
one. You could do that as well, and have Command
inherit from Message<Command>
instead, but that would make your inheritance tree look a whole lot different (i.e. classes with the same message length wouldn't have the same base class anymore).
You could of course add another layer of indirection in there, where you inherit from e.g. MessageBase<Command>
which in turn inherits from Message<MessageSize<Command>::size>
.
Upvotes: 3