2

I would like to use CRTP for a performance-sensitive section of my code. However, my base class has a bitset, which size depends on the derived class. I was hoping that something like that would work:

template <typename Derived>
class Base {
protected:
    std::bitset<Derived::bsize> data_;
};

class Foo : public Base<Foo> {
public:
    constexpr static size_t bsize = 2;
};

but the compiler complains: "no member bsize in Foo". I guess I could solve my problem by templating the bitset length too in the base class:

template <typename Derived, size_t size>
class Base {
protected:
    std::bitset<size> data_;
};

class Foo : public Base<Foo,2> { ... };

In the future, I might want to have more complex expressions to compute the bitset length. Is there a way to get the job done using constexpr functions? (closer in spirit to my first non-working solution) Thanks.

Touloudou
  • 2,079
  • 1
  • 17
  • 28

1 Answers1

2

The answer is: You cannot do this with CRTP in C++. What's happening is when Base<Foo> is instantiated, Foo::bsize does not exist yet. This is because that happens when the compiler sees Base<Foo> on the Foo class, which is before your {. It's not quite as simple as that, but that's the general idea.

There is a workaround to do what you want here, which is to bundle all the necessary information in a class, then supply it as a template parameter. I do not know the name of this pattern (I've seen "baggage class," but that feels pejorative), but you can find examples of this in the standard library, such as std::char_traits.

class FooTraits {
    constexpr static size_t bsize = 2;
};

template <class Traits = FooTraits>
class BasicFoo {
protected:
    std::bitset<Traits::bsize> data_;
};

Traits are pretty flexible -- you can just drop a static constexpr function into your FooTraits to calculate whatever you need to. In your case, the derived class would pass a derived-type-specific version of FooTraits to the Traits template argument of BasicFoo.

It is worth noting that your milage may vary. While flexible, the problem with traits is that someone wanting to implement the concept of FooTraits needs to make sure they implement all the things BasicFoo needs or they will get a horrible compilation error (in C++20, this is helped via concepts). Without careful thought, traits can become a dumping ground of things, which makes implementing an alternative FooTraits even harder.

Travis Gockel
  • 26,877
  • 14
  • 89
  • 116