C++ Recursive Template Specialization - Missing Proper Parameter Pack Expansion

55 views Asked by At

Background & Goal

I wanted to create some sort of data utility, that stores an std::stringstream, as well as the format, the content is currently in. This shall give you type safety and IntelliSense as to that content representation your data currently has. Basically, it creates or removes a template parameter for every operation (compression, encoding, parsing, etc.):

// content representations/states
enum class ContentTypes
{
    binary = 1,
    text = 2,
    json = 3,
    zlib = 4
};

template <ContentTypes... PreviousTypes>
class X
{
public:
    X<ContentTypes::zlib, PreviousTypes...> zlib()
    {
        return X<ContentTypes::zlib, PreviousTypes...>();
    }
};

template <ContentTypes... PreviousTypes>
class X<ContentTypes::zlib, PreviousTypes...> : public X<(ContentTypes::zlib, PreviousTypes)...>
{
public:
    X<PreviousTypes...> decompress()
    {
        return X<PreviousTypes...>();
    }
};

#include <string>
#include <iostream>


int main()
{
    /// deterministic and compile-time safety as to what content type the data has
    X<ContentTypes::binary> foo;                    // some binary data (X<ContentTypes::binary>)
    auto compressed = foo.zlib();                   // X<ContentTypes::zlib, ContentTypes::binary>
    auto compressed2 = compressed.zlib();           // X<ContentTypes::zlib, ContentTypes::zlib, ContentTypes::binary>
    auto decompressed = compressed2.decompress();   // X<ContentTypes::zlib, ContentTypes::binary>
    auto decompressed2 = decompressed.decompress(); // X<ContentTypes::binary>
    return 0;
}

Problem

as you may have noticed, there is a mistake in the template specialization:

class X<ContentTypes::zlib, PreviousTypes...> : public X<(ContentTypes::zlib, PreviousTypes)...>

which results in wrong content types. If you try to compile the above code, you will get an error that the variable decompressed (which is actually of type X<ContentTypes::binary>, instead of X<ContentTypes::zlib, ContentTypes::binary>) has no attribute decompress. That is correct, since X<ContentTypes::binary> has no function called decompress. I don't know how to "inherit" the methods of the upper template instantiation to the specialization in the lower one without ending up with a recursive template.

The resulting GCC error:

.../src/main.cpp:52:39: error: 'class X<ContentTypes::binary>' has no member named 'decompress'
   52 |     auto decompressed2 = decompressed.decompress(); // X<ContentTypes::binary>
      |                                       ^~~~~~~~~~

Attempts so far

  • I've searched some literature on C++ template specialization and parameter pack expansion, but to no success
  • ChatGPT was great for discovering new ways to break my code and offered the (ContentTypes::zlib, PreviousTypes)... expansion, which I think isn't correct at all because it just performs comma operator...

If you need more information, just ask in a comment. Thanks in advance! :)

1

There are 1 answers

0
TheClockTwister On

Thanks for the helpful comments! I received a hint that brought me to a working solution:

Full code

enum class ContentTypes
{
    binary = 1,
    text = 2,
    json = 3,
    zlib = 4
};


template <ContentTypes... PreviousTypes>
class X;

template <ContentTypes... PreviousTypes>
class XBase
{
public:
    X<ContentTypes::zlib, PreviousTypes...> zlib()
    {
        return X<ContentTypes::zlib, PreviousTypes...>();
    }
};

template <ContentTypes... PreviousTypes>
class X : public XBase<PreviousTypes...>
{
};

template <ContentTypes... PreviousTypes>
class X<ContentTypes::zlib, PreviousTypes...> : public XBase<ContentTypes::zlib, PreviousTypes...>
{
public:
    X<PreviousTypes...> decompress()
    {
        return X<PreviousTypes...>();
    }
};


#include <string>
#include <iostream>

int main()
{
    /// deterministic and compile-time safety as to what content type the data has
    X<ContentTypes::binary> foo;                    // some binary data (X<ContentTypes::binary>)
    auto compressed = foo.zlib();                   // X<ContentTypes::zlib, ContentTypes::binary>
    auto compressed2 = compressed.zlib();           // X<ContentTypes::zlib, ContentTypes::zlib, ContentTypes::binary>
    auto decompressed = compressed2.decompress();   // X<ContentTypes::zlib, ContentTypes::binary>
    auto decompressed2 = decompressed.decompress(); // X<ContentTypes::binary>
    return 0;
}

Explaination

The inheritance done in a proper way would create a recursive template, which aborts compilation, since the name of the derived and parent class are identical (X). Just by renaming the parent class to Xbase and inheriting from that, this problem is solved. Now you just need to create a new base template class X that also inherits from XBase to make things complete.