ilya1725
ilya1725

Reputation: 4938

Static polymorphism with constant template

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

Answers (3)

Swift - Friday Pie
Swift - Friday Pie

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

Serial Lazer
Serial Lazer

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

AVH
AVH

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

Related Questions