One of the beatiful things of the Fediverse is the amount of interesting people and the stimulating dicussions they fuel.

This article stems from a minithread by @tess​@mastodon.social complaining noting that there are things that C++ templates cannot achieve yet, and for which one still needs to use preprocessor macros, which has its own share of problems.

I'll leave it up to the thread to give details, but the problem space is the following: we want to be able to assign properties to objects in such a way that, if a class C has a property Prop of type T then the object should reply to a common set of methods (member functions) such as:

T C::GetProp();
void C::SetProp(T)
Subscription C::AddPropChangedCallback(Callback)

and so on. The gimmick here is that for each property you need all these functions, and they all do exactly the same thing, for different variables. And the reason why this cannot be done purely via templates is that the name/​value of the parameter used to instantiate a template cannot be used to build the names of the functions.

I remarked that this is actually possible using templates, as long as we allow for a slightly different syntax and go deep into meta-programming territory.

The objective is to build a “generic property mixin” that can be used as base class for any number of properties, and with a slight change in syntax:

T C::Get<Prop>();
void C::Set<Prop>(T)
Subscription C::AddChangedCallback<Prop>(Callback)

For simplicity, we will limit ourselves to the getter and setter function. The code assumes C++14 (with some effort the requirement can be lowered to C++11, and with a lot of effort even to C++98), and is licensed under the GPLv3.

#include <type_traits>

/* A structure encapsulating a list of types.
   We will use this to determine if a type is present
   in the list or not */
template<class... List>
struct type_list;

/* Generic case */
template<class Head, class... Tail>
struct type_list<Head, Tail...> {
    /* Type alias that maps to Head if Needle matches it, and looks
       for Needle in the Tail of the list otherwise */
    template<class Needle, class Fail>
    using find_type = std::conditional_t<
        std::is_same<Needle, Head>::value,
        Head,
        typename type_list<Tail...>::template find_type<Needle, Fail> >;
};

/* Special case for a list of length one */
template<class Head>
struct type_list<Head> {
    /* In this case the Fail type is returned if Needle isn't found
       (i.e. if it doesn't match Head */
    template<class Needle, class Fail>
    using find_type = std::conditional_t<
        std::is_same<Needle, Head>::value,
        Head,
        Fail>;
};

/* Properties are defined by tag classes (declared but not defined) */

/* The special tag class PropertyNotFound will be used
   to return meaningful errors when trying to access a property
   for an object whose class does not have it. */
struct PropertyNotFound;

/* Property traits class:
   this defines the characteristics (traits) of a property.
   It should define at least its `type`, but of course it can be expanded
   for more introspection (e.g. it could include a name, etc.) */
template<class P> struct prop_traits {};

/* Basic mixin for a single property:
   defines unqualified get() and set() */
template<class P>
struct prop_mixin_base {
    using type = typename prop_traits<P>::type;
    type value;

    type get() { return value; }

    void set(type const& in) { value = in; }
};

/* The actual mixin for the properties is a variadic template,
   taking all the property tag classes as parameters. */
template<class... Ps>
class prop_mixin :
    /* derive from the basic mixin for each property */
    public prop_mixin_base<Ps>...
{
    /* Type alias to simplify syntax */
    using props = type_list<Ps...>;

    /* Type alias that maps a property to the corresponding mixin,
       falling back to PropertyNotFound */
    template<class U>
    using find_base = prop_mixin_base<
        typename props::template find_type<U, PropertyNotFound>
    >;

    /* Type alias that maps a property to its type,
       to simplify syntax */
    template<class U>
    using prop_type = typename find_base<U>::type;

public:

    /* User-facing interface: template functions for getter and setter
       that map to the corresponding basic mixin unquaified functions */
    template<class U>
    prop_type<U> get() { return find_base<U>::get(); }
    template<class U>
    void set(prop_type<U> const& in) { find_base<U>::set(in); }
};

/* Example usage: defines a Width and Height property, and an Unused one */
class Width; template<> struct prop_traits<Width> { using type = int ; };
class Height; template<> struct prop_traits<Height> { using type = int ; };
class Unused; template<> struct prop_traits<Unused> { using type = int ; };

/* A class C with Width and Height property */
class C : public prop_mixin<Width, Height> { };

#include <iostream>
int main()
{
    C c;
    c.set<Width>(1);
    c.set<Height>(2);
    std::cout << c.get<Height>() << std::endl;
    std::cout << c.get<Width>() << std::endl;
    // c.set<Unused>(3); // errors out complaining about a PropertyNotFound
}