Search code examples
c++c++11templatestemplate-templates

Partially filled template as parameter for template template


I have a template with two template arguments (MyCollection) and another template (TCTools) expecting a template with one template argument as template argument.

I defined a "bridge" (TypedCollection) to get a template with one parameter from MyCollection and an argument for it's first parameter with the aim of passing it to the template template.

This works fine, if I use the bridge with a fixed type as argument, but calling it from yet another template with the argument of that other template will not compile.

#include <iostream>
using std::size_t;

template <class Scalar, size_t size>
struct MyCollection
{
    MyCollection()
    {
        std::cout << "Made collection"
                  << std::endl
                  << "  " << __PRETTY_FUNCTION__
                  << std::endl;
    }
};

template <class Scalar>
struct TypedCollection
{
    template <size_t size>
    using value = MyCollection<Scalar, size>;
};

template <template <size_t> class TC>
struct TCTools
{
    static TC<10> *make_10_sized()
    {
        return new TC<10>();
    }
};

template <class S>
void test()
{
    // Will not compile
    TCTools<TypedCollection<S>::value>::make_10_sized();
}

int main()
{
    // works
    TCTools<TypedCollection<int>::value>::make_10_sized();

    test<int>();
    return 0;
}

GCC gives the following note:

expected a class template, got ‘TypedCollection<S>::value’

The whole thing has left me very confused. Why is the call in test() not compiling, while the one in main() works just as expected? Is it possible to get test to work?


Solution

  • TCTools<TypedCollection<S>::template value>::make_10_sized()
    

    much like typename (but confusingly different), you have to disambiguate what value is, or the compiler assumes it is a value and not a type or template.

    It does this before S is substituted. In theory, a specialization of TypedCollection<S> could make value anything at all, and the compiler doesn't try to guess.


    As an aside, if you end up doing more metaprogramming, you'll find having template, non-type and type template arguments to be a real pain.

    One approach is to turn all 3 into types.

    template<template<class...>class Z>
    struct ztemplate_t {
      template<class...Ts> using apply=Z<Ts...>;
    };
    // C++17
    template<auto x>
    using zvalue_t = std::integral_constant< std::decay_t<decltype(x)>, x >;
    // C++11
    template<std::size_t x>
    using zsize_t = std::integral_constant< std::size_t, x >;
    

    Then we can write templates like apply that take a ztemplate as the first argument and apply it to the rest of the arguments, then zapply which is ztemplate<apply>.

    Once that heavy lifting is done, TypedCollection<T> becomes partial_apply_t< zMyCollection_t, T>.


    Another is to turn all 3 into constexpr values and do constexpr value-style metaprogramming.

    Under this, we end up with type_t< decltype(zpartial_apply( zMyCollection, tag<T> ))>.


    But for small one-off libraries, both of these are overkill.