One of my goals while creating a custom game engine is to avoid using the C++ standard library. This post won’t discuss why, but one consequence of that goal that I have run into during this early phase of the project is that I have had to recreate some fundamental machinery that the standard library provides. By “recreate” I don’t mean that I have figured things out myself starting from nothing but first principles, but instead that I have had to look at existing documentation and implementations to understand how something works and then reimplement it in my own style. It has been fun to learn a bit more about how this corner of C++ metaprogramming works; although I have used these features (some of them frequently) I have only vaguely understood how they might have actually been implemented.
Doing this has also had the interesting consequence of presenting me with challenges to my existing coding style and I have had to expand and adapt. This post gives some examples of changes to my coding style that I have been experimenting with in order to accommodate type traits.
Existing Style
My current style uses a lowercase single letter prefix to indicate type names, meaning that if an identifier is spelled e.g. jSomething
or pSomething
the reader can immediately know that those names identify types, even without knowing what the j
or p
might mean (and, to be clear, those are just examples, and neither the j
nor the p
prefix exists (yet)).
Class names start with a c
prefix:
class cMyClass;
class cSomeOtherClass;
Struct names start with an s
prefix, base class names start with an i
prefix (for “interface”, where my goal is that leaf classes are the only ones that are ever instantiated), and enumeration names start with an e
prefix:
// When I use a struct instead of a class it means:
// * The member variables are public instead of private
// * The member variables use a different naming scheme
struct sMyStruct;
// The reader can tell that the following is a base class
// (and conceptually an abstract base class)
// just from the type name alone:
class iMyBaseClass;
// Using this enum convention makes it
// less annoying to use scope enumerations
// because the prefix makes it instantly identifiable
// and so the identifiers can be chosen accordingly:
enum class eMyState
{
On,
Off,
};
Type names start with a t
prefix, e.g.:
// Creating a type alias:
using tMySize = int;
// In templates:
template<typename tKey, typename tValue>
class cMyMap;
(I don’t like the common convention of using single letters (e.g. T
and U
) in templates, and find that it makes code much harder to read for me personally, similarly to how I feel about single letter variable names. This strongly-held conviction has been challenged in some cases by working with type traits, which I discuss below.)
Type Names
Some of the type trait information that I have needed are expressions that are types. The standard uses a _t
suffix for this, which is a helper type of a struct, but I have never loved this convention. In my code so far I have used a t
prefix for cases like, and hidden the associated struct in a details
namespace:
// tNoConst -> Type without a const qualifier
namespace Types::detail
{
template<typename T> struct sNoConst { using type = T; };
template<typename T> struct sNoConst<const T> { using type = T; };
}
template<typename T>
using tNoConst = typename Types::detail::sNoConst<T>::type;
// An example of this being used:
const int someConstVariable = 0;
tNoConst<decltype(someConstVariable)> someMutableVariable;
someMutableVariable = 0;
This use of the t
prefix is not really any different from how I had already named types, and I find that it fits in naturally when used in code. Eagle-eyed readers may notice, however, that I am using a single T
as a type, which I had mentioned is something that I strongly dislike and claimed that I don’t do!
In all of my previous template programming I have always been able to come up with some name that made sense in context, even if it was sometimes generic like tValue
. While working on these type traits, however, I realized that there are cases (like shown above) where the type really could be any type. I considered tType
, but that seemed silly. I considered tAny
, and I might still end up changing my mind and refactoring the code to that (or something similar). For now, though, I have capitulated and am using just the T
for fully generic fundamental type trait building blocks like the ones discussed in this post (in other code, though, I still intend to strictly adhere to my give-the-type-a-name rule).
Value Names
Some of the type trait information that I have needed are expressions that are values. The standard uses a _v
suffix for this, but I have never loved this convention. In fact, I’m not sure that I really understand this convention; unlike with _t
where there needs to be some underlying struct for the metaprogramming implementation to work it doesn’t seem like values need this (at least, the ones that I have recreated so far haven’t needed an underlying struct).
I did struggle a bit with how to name these, however. My existing coding convention would prefix global variables names with g_
(that will have to be discussed in a separate post), but these type trait variables feel different from traditional global variables to me. In my mind they conceptually feel more like functions than variables, but functions that I call with <>
instead of ()
. I wanted some alternate convention to make them visually distinct from standard variables.
After some experimentation I eventually settled on keeping the v
from the standard but making it a prefix instead of a suffix, and I have been pretty happy so far with the result:
// vIsConst -> Whether a type is const-qualified
template<typename T>
constexpr bool vIsConst = false;
template<typename T>
constexpr bool vIsConst<const T> = true;
// An example of this being used:
if constexpr (vIsConst<decltype(someVariable)>)
{
// Stuff
}
This convention has added a new member to my pantheon of prefixes, but it has felt natural and like a worthy addition so far. As an additional unexpected bonus it has also given me a new convention for naming template non-type parameters:
// My new convention:
template<typename tSomeType, bool vSomeCondition>
class myClass1;
// My previous convention, which I never loved:
template<typename tSomeType, bool tSomeCondition>
class myClass2;
Having a new way of unambiguously specifying compile-time values has improved the readability of my code for me.
Concept Names
I have encountered one case where I wanted to make a named constraint and I had to think about what to name it. I don’t have enough experience yet to know whether my initial attempt is something that I will end up liking, but this is what I have come up with:
// rIsBaseOf -> Enforces vIsBaseOf
template<typename tDerived, typename tBase>
concept rIsBaseOf = vIsBaseOf<tBase, tDerived>;
// An example of this being used:
template<rIsBaseOf<iBaseClass> tSomeClass>
class cMyConstrainedClass;
I couldn’t use c
for “constraint” or “concept” because that was already taken for classes. I finally settled on r
for “restraint” (kind of like a mix of “constraint” and “restrict”, with a suggestion of requires
) and I don’t hate it so far but I also don’t love it. It feels like it is good enough to do the job for me in my own code, but it also feels like maybe there’s a better convention that I haven’t thought of yet.