Search code examples
c++templatesvariadic-templates

C++ Variadic Type Instantiation


I have been hunting for a solution for this for a while, and have not been able to find a satisfying solution to this problem:

Suppose I have a variadic type:

template<typename... layers_t>
class Layer_Aggregate
{

};

And I have some alias of the above like this:

using stack_t = Layer_Aggregate<
    Matrix<3,3>,
    Matrix<3,3>
>;
  1. How would I go about properly instantiating an object of type layers_t? I am unsure as to what the best way to represent and instantiate the Matrix<3,3>s is. Is there a design pattern to solve this problem? I can easily instantiate stack_t, but it won't ever contain any Matrix<3,3> data because my class does not contain a data member.
  2. My end goal is to take advantage of template expressions and allow for operations such as multiplying all matrices in a single line. I am unsure if this changes the answer to the above question. The library I am using evaluates the expression on assignment.

Solution

  • There's a couple of ways to define a variadic function that folds over a bunch of objects:

    Using Fold Expressions Directly

    This got way easier in C++17 if that's available to you. Just define an operator for your matrix:

    Matrix<3, 3> operator *(const Matrix<3, 3>& lhs, const Matrix<3, 3>& rhs) { /* ... */ }
    

    Then it's as easy as using a fold expression

    template <typename... Ts>
    auto aggregate(Ts&&... ts) {
        return (ts * ...);
    }
    

    Which will do a right fold over all the matrices until they're aggregated into one.

    Usage:

    Matrix<3, 3> a = /*...*/;
    Matrix<3, 3> b = /*...*/;
    Matrix<3, 3> c = /*...*/;
    auto abc = aggregate(a, b, c);
    

    Demo: https://godbolt.org/z/uf54Hk

    Using Fold Expressions Via A Wrapper

    What if your reduce function isn't available as an operator? For instance:

    Matrix<3, 3> multiply(const Matrix<3, 3>& lhs, const Matrix<3, 3>& rhs)
    

    You can still wrap it in a helper struct and define an operator for that:

    template <auto Op, typename T>
    struct RefWrapper { const T& ref; };
    
    template <auto Op, typename T, typename U>
    auto operator *(RefWrapper<Op, T> t, RefWrapper<Op, U> u) {
        return RefWrapper<Op, decltype(Op(t.ref, u.ref))>{ Op(t.ref, u.ref) };
    }
    
    template <auto Op, typename... Ts>
    auto aggregate(Ts&&... ts) {
        return (RefWrapper<Op, Ts>{ ts } * ...).ref;
    }
    

    Demo: https://godbolt.org/z/APhT-v

    C++11 and C++14 - Recursive Templates

    In C++11/14, it's a bit more difficult since you have to manually recurse through each argument. You also can't auto-deduce function pointer signatures, so you have to add a separate template argument to deduce the field.

    Start with a placeholder struct.

    template <typename Sig, Sig Op, typename... Ts>
    struct LayerAggregate;
    

    Then partially specialize for the recursive case. This will pluck off the first argument, and multiply it with the aggregate of the remaining arguments. We can also use the same partial specialization to deduce the return type of the operator:

    template <typename T, T (*Op)(const T&, const T&), typename TFirst, typename... TRest>
    struct LayerAggregate<T(*)(const T&, const T&), Op, TFirst, TRest...> {
        T operator()(TFirst&& first, TRest&&... rest) {
            T restAggregate = LayerAggregate<T(*)(const T&, const T&), Op, TRest...>{}(std::forward<TRest>(rest)...);
            return Op(first, restAggregate);
        }
    };
    

    Finally, add a terminal case, to stop recursing when it gets to the last item:

    template <typename T, T (*Op)(const T&, const T&), typename TLast>
    struct LayerAggregate<T(*)(const T&, const T&), Op, TLast> {
        T operator()(TLast&& last) {
            return last;
        }
    };
    

    If you're using C++14, the wrapper is easy if you ask the compiler to deduce the return type:

    template <typename Sig, Sig Op, typename... Ts>
    auto Aggregate(Ts&&... ts) {
        return LayerAggregate<Sig, Op, Ts...>{}(std::forward<Ts>(ts)...);
    }
    

    For C++11, you still have to deduce it yourself though:

    template <typename Sig, Sig Op, typename... Ts>
    struct OpResult;
    
    template <typename Sig, Sig Op, typename T, typename... Ts>
    struct OpResult<Sig, Op, T, Ts...> {
        using Type = decltype(Op(std::declval<T>(), std::declval<T>()));
    };
    
    template <typename Sig, Sig Op, typename... Ts>
    using OpResultT = typename OpResult<Sig, Op, Ts...>::Type;
    
    template <typename Sig, Sig Op, typename... Ts>
    OpResultT<Sig, Op, Ts...> Aggregate(Ts&&... ts) {
        return LayerAggregate<Sig, Op, Ts...>{}(std::forward<Ts>(ts)...);
    }
    

    Finally, to call, you just have to pass the function signature as well as the function:

    Matrix<3, 3> a = /*...*/;
    Matrix<3, 3> b = /*...*/;
    Matrix<3, 3> c = /*...*/;
    auto abc = Aggregate<decltype(&multiply), &multiply>(a, b, c);
    

    Demo: https://godbolt.org/z/49wb7H