Date: Sat, 19 Apr 2025 23:58:54 +0100
This past week in the thread about "std::arithmetic", we've been
talking about different ways of tagging a class.
Here's a class that has a tag that will be inherited by all derived classes:
#include <type_traits>
class MyClass {
public:
typedef int tag_arithmetic;
};
class MyDerivedClass : public MyClass {
};
template<typename T>
void Func(void)
{
static_assert( std::is_same_v<int, typename T::tag_arithmetic> );
}
int main(void)
{
Func<MyDerivedClass>();
}
The derived class can opt out of the tag as follows:
class MyDerivedClass : public MyClass {
public:
typedef void tag_arithmetic;
};
And here's a way of writing a class with a tag which is not implicitly
inherited by derived classes:
class MyClass {
public:
typedef MyClass tag_arithmetic;
};
class MyDerivedClass : public MyClass {
};
template<typename T>
void Func(void)
{
static_assert( std::is_same_v<T, typename T::tag_arithmetic> );
}
int main(void)
{
Func<MyDerivedClass>();
}
And the derived class can opt in as follows:
class MyDerivedClass : public MyClass {
public:
typedef MyDerivedClass tag_arithmetic;
};
An alternative to using 'typedef' for creating tags is to use a static
member boolean as follows:
#include <type_traits>
class MyClass {
public:
static constexpr bool tag_arithmetic = true;
};
class MyDerivedClass : public MyClass {
};
template<typename T>
void Func(void)
{
static_assert( T::tag_arithmetic );
}
int main(void)
{
Func<MyDerivedClass>();
}
And the derived class can opt out as follows:
class MyDerivedClass : public MyClass {
public:
static constexpr bool tag_arithmetic = false;
};
And another way of tagging a class is to use an empty base class as follows:
class tag_arithmetic {};
class MyClass : public tag_arithmetic {};
class MyDerivedClass : public MyClass {};
template<typename T>
void Func(void)
{
static_assert( std::is_base_of_v<tag_arithmetic, T> );
}
int main(void)
{
Func<MyDerivedClass>();
}
(This particular method could get really interesting at runtime with
'dynamic_cast')
In all the ways shown so far of tagging a class, the tag is part of
the class definition. What this means is that if a class is inside a
header file, and if you want to add a tag to that class, then you must
edit the header file. There is another method of tagging though that
allows you to leave the class definition intact, as follows:
template<typename T>
constexpr bool tag_arithmetic = false;
class MyClass {};
template<>
constexpr bool tag_arithmetic<MyClass> = true;
class MyDerivedClass : public MyClass {};
template<>
constexpr bool tag_arithmetic<MyDerivedClass> = true;
template<typename T>
void Func(void)
{
static_assert( tag_arithmetic<T> );
}
int main(void)
{
Func<MyDerivedClass>();
}
As you can see in the above code snippet, the tag is not automatically
inherited by derived classes.
I want to discuss the possibility of standardising tagging. I think
any given tag should have the following configurable attributes:
1) Tag is inherited by default, or not inherited by default
2) Tag is within class definition, or outside class definition
So here's a tag that's automatically inherited by derived classes:
class MyClass {
public:
_Tag arithmetic;
};
You can consider the above class definition to be short-hand for:
class MyClass {
public:
_Tag arithmetic = true;
};
Here's a tag that's not automatically inherited by derived classes:
class MyClass {
public:
_Tag arithmetic : final;
};
which again is short-hand for:
class MyClass {
public:
_Tag arithmetic : final = true;
};
Here is how you write a derived class and tell the compiler "This
class has the tag if any of its bases have the tag" -- so that you can
opt into a tag which is marked 'final' in the base class as follows:
class MyClass {
public:
_Tag arithmetic : final;
};
class MyDerivedClass : public MyClass {
public:
_Tag arithmetic = ?;
};
And if you don't want to edit the header file for a class, then you
can add the tag after the class definition as follows:
class MyClass {};
MyClass << _Tag arithmetic : final;
class MyDerivedClass : public MyClass {};
MyDerivedClass << _Tag arithmetic = ?;
And here's how a derived class opts out of a base class's tag:
class MyClass {
public:
_Tag arithmetic;
};
class MyDerivedClass : public MyClass {
public:
_Tag arithmetic = false;
};
Note that the expression after the '=' operator can be any constant
expression that evaluates to a boolean, for example:
class MyClass {
public:
_Tag arithmetic = is_debugging_enabled && is_built_as_static_executable;
};
Now a few of you might be thinking right now:
So you've shown us 5 different ways of tagging a class, so what's
good about increasing that number to 6? Well here's what I'm thinking:
Point No. 1: If the 6th tagging system is clearly superior to
the other 5, then people will adopt it and abandon the other 5.
Point No. 2: Use of the new keyword "_Tag" makes it more
explicit and easier to figure out exactly what the code is doing at a
glance.
Point No. 3: Once tagging is standardised, the documentation
for an SDK library can have a comment such as "The type T must have
the arithmetic tag", and the programmer doesn't have to scratch their
head wondering which of the 5 tagging systems is used -- instead they
know it's the standardised system.
And finally: The reflection system would allow constexpr iteration
through a class's tags as follows:
for ( auto const &t = std::get_tags( typeid(T) ) )
{
cout << "Tag: " << t.name() << " Inherited from base: " <<
(t.was_inherited() ? "yes" : "no") << endl;
}
FIN.
talking about different ways of tagging a class.
Here's a class that has a tag that will be inherited by all derived classes:
#include <type_traits>
class MyClass {
public:
typedef int tag_arithmetic;
};
class MyDerivedClass : public MyClass {
};
template<typename T>
void Func(void)
{
static_assert( std::is_same_v<int, typename T::tag_arithmetic> );
}
int main(void)
{
Func<MyDerivedClass>();
}
The derived class can opt out of the tag as follows:
class MyDerivedClass : public MyClass {
public:
typedef void tag_arithmetic;
};
And here's a way of writing a class with a tag which is not implicitly
inherited by derived classes:
class MyClass {
public:
typedef MyClass tag_arithmetic;
};
class MyDerivedClass : public MyClass {
};
template<typename T>
void Func(void)
{
static_assert( std::is_same_v<T, typename T::tag_arithmetic> );
}
int main(void)
{
Func<MyDerivedClass>();
}
And the derived class can opt in as follows:
class MyDerivedClass : public MyClass {
public:
typedef MyDerivedClass tag_arithmetic;
};
An alternative to using 'typedef' for creating tags is to use a static
member boolean as follows:
#include <type_traits>
class MyClass {
public:
static constexpr bool tag_arithmetic = true;
};
class MyDerivedClass : public MyClass {
};
template<typename T>
void Func(void)
{
static_assert( T::tag_arithmetic );
}
int main(void)
{
Func<MyDerivedClass>();
}
And the derived class can opt out as follows:
class MyDerivedClass : public MyClass {
public:
static constexpr bool tag_arithmetic = false;
};
And another way of tagging a class is to use an empty base class as follows:
class tag_arithmetic {};
class MyClass : public tag_arithmetic {};
class MyDerivedClass : public MyClass {};
template<typename T>
void Func(void)
{
static_assert( std::is_base_of_v<tag_arithmetic, T> );
}
int main(void)
{
Func<MyDerivedClass>();
}
(This particular method could get really interesting at runtime with
'dynamic_cast')
In all the ways shown so far of tagging a class, the tag is part of
the class definition. What this means is that if a class is inside a
header file, and if you want to add a tag to that class, then you must
edit the header file. There is another method of tagging though that
allows you to leave the class definition intact, as follows:
template<typename T>
constexpr bool tag_arithmetic = false;
class MyClass {};
template<>
constexpr bool tag_arithmetic<MyClass> = true;
class MyDerivedClass : public MyClass {};
template<>
constexpr bool tag_arithmetic<MyDerivedClass> = true;
template<typename T>
void Func(void)
{
static_assert( tag_arithmetic<T> );
}
int main(void)
{
Func<MyDerivedClass>();
}
As you can see in the above code snippet, the tag is not automatically
inherited by derived classes.
I want to discuss the possibility of standardising tagging. I think
any given tag should have the following configurable attributes:
1) Tag is inherited by default, or not inherited by default
2) Tag is within class definition, or outside class definition
So here's a tag that's automatically inherited by derived classes:
class MyClass {
public:
_Tag arithmetic;
};
You can consider the above class definition to be short-hand for:
class MyClass {
public:
_Tag arithmetic = true;
};
Here's a tag that's not automatically inherited by derived classes:
class MyClass {
public:
_Tag arithmetic : final;
};
which again is short-hand for:
class MyClass {
public:
_Tag arithmetic : final = true;
};
Here is how you write a derived class and tell the compiler "This
class has the tag if any of its bases have the tag" -- so that you can
opt into a tag which is marked 'final' in the base class as follows:
class MyClass {
public:
_Tag arithmetic : final;
};
class MyDerivedClass : public MyClass {
public:
_Tag arithmetic = ?;
};
And if you don't want to edit the header file for a class, then you
can add the tag after the class definition as follows:
class MyClass {};
MyClass << _Tag arithmetic : final;
class MyDerivedClass : public MyClass {};
MyDerivedClass << _Tag arithmetic = ?;
And here's how a derived class opts out of a base class's tag:
class MyClass {
public:
_Tag arithmetic;
};
class MyDerivedClass : public MyClass {
public:
_Tag arithmetic = false;
};
Note that the expression after the '=' operator can be any constant
expression that evaluates to a boolean, for example:
class MyClass {
public:
_Tag arithmetic = is_debugging_enabled && is_built_as_static_executable;
};
Now a few of you might be thinking right now:
So you've shown us 5 different ways of tagging a class, so what's
good about increasing that number to 6? Well here's what I'm thinking:
Point No. 1: If the 6th tagging system is clearly superior to
the other 5, then people will adopt it and abandon the other 5.
Point No. 2: Use of the new keyword "_Tag" makes it more
explicit and easier to figure out exactly what the code is doing at a
glance.
Point No. 3: Once tagging is standardised, the documentation
for an SDK library can have a comment such as "The type T must have
the arithmetic tag", and the programmer doesn't have to scratch their
head wondering which of the 5 tagging systems is used -- instead they
know it's the standardised system.
And finally: The reflection system would allow constexpr iteration
through a class's tags as follows:
for ( auto const &t = std::get_tags( typeid(T) ) )
{
cout << "Tag: " << t.name() << " Inherited from base: " <<
(t.was_inherited() ? "yes" : "no") << endl;
}
FIN.
Received on 2025-04-19 22:59:03