What are some uses of template template parameters?

后端 未结 10 1006
无人共我
无人共我 2020-11-22 03:41

I\'ve seen some examples of C++ using template template parameters (that is templates which take templates as parameters) to do policy-based class design. What other uses do

相关标签:
10条回答
  • 2020-11-22 03:52

    It improves readability of your code, provides extra type safety and save some compiler efforts.

    Say you want to print each element of a container, you can use the following code without template template parameter

    template <typename T> void print_container(const T& c)
    {
        for (const auto& v : c)
        {
            std::cout << v << ' ';
        }
        std::cout << '\n';
    }
    

    or with template template parameter

    template< template<typename, typename> class ContainerType, typename ValueType, typename AllocType>
    void print_container(const ContainerType<ValueType, AllocType>& c)
    {
        for (const auto& v : c)
        {
            std::cout << v << ' ';
        }
        std::cout << '\n';
    }
    

    Assume you pass in an integer say print_container(3). For the former case, the template will be instantiated by the compiler which will complain about the usage of c in the for loop, the latter will not instantiate the template at all as no matching type can be found.

    Generally speaking, if your template class/function is designed to handle template class as template parameter, it is better to make it clear.

    0 讨论(0)
  • 2020-11-22 03:56

    Actually, usecase for template template parameters is rather obvious. Once you learn that C++ stdlib has gaping hole of not defining stream output operators for standard container types, you would proceed to write something like:

    template<typename T>
    static inline std::ostream& operator<<(std::ostream& out, std::list<T> const& v)
    {
        out << '[';
        if (!v.empty()) {
            for (typename std::list<T>::const_iterator i = v.begin(); ;) {
                out << *i;
                if (++i == v.end())
                    break;
                out << ", ";
            }
        }
        out << ']';
        return out;
    }
    

    Then you'd figure out that code for vector is just the same, for forward_list is the same, actually, even for multitude of map types it's still just the same. Those template classes don't have anything in common except for meta-interface/protocol, and using template template parameter allows to capture the commonality in all of them. Before proceeding to write a template though, it's worth to check a reference to recall that sequence containers accept 2 template arguments - for value type and allocator. While allocator is defaulted, we still should account for its existence in our template operator<<:

    template<template <typename, typename> class Container, class V, class A>
    std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
    ...
    

    Voila, that will work automagically for all present and future sequence containers adhering to the standard protocol. To add maps to the mix, it would take a peek at reference to note that they accept 4 template params, so we'd need another version of the operator<< above with 4-arg template template param. We'd also see that std:pair tries to be rendered with 2-arg operator<< for sequence types we defined previously, so we would provide a specialization just for std::pair.

    Btw, with C+11 which allows variadic templates (and thus should allow variadic template template args), it would be possible to have single operator<< to rule them all. For example:

    #include <iostream>
    #include <vector>
    #include <deque>
    #include <list>
    
    template<typename T, template<class,class...> class C, class... Args>
    std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
    {
        os << __PRETTY_FUNCTION__ << '\n';
        for (auto const& obj : objs)
            os << obj << ' ';
        return os;
    }
    
    int main()
    {
        std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
        std::cout << vf << '\n';
    
        std::list<char> lc { 'a', 'b', 'c', 'd' };
        std::cout << lc << '\n';
    
        std::deque<int> di { 1, 2, 3, 4 };
        std::cout << di << '\n';
    
        return 0;
    }
    

    Output

    std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = float, C = vector, Args = <std::__1::allocator<float>>]
    1.1 2.2 3.3 4.4 
    std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = char, C = list, Args = <std::__1::allocator<char>>]
    a b c d 
    std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>]
    1 2 3 4 
    
    0 讨论(0)
  • 2020-11-22 03:57

    Say you're using CRTP to provide an "interface" for a set of child templates; and both the parent and the child are parametric in other template argument(s):

    template <typename DERIVED, typename VALUE> class interface {
        void do_something(VALUE v) {
            static_cast<DERIVED*>(this)->do_something(v);
        }
    };
    
    template <typename VALUE> class derived : public interface<derived, VALUE> {
        void do_something(VALUE v) { ... }
    };
    
    typedef interface<derived<int>, int> derived_t;
    

    Note the duplication of 'int', which is actually the same type parameter specified to both templates. You can use a template template for DERIVED to avoid this duplication:

    template <template <typename> class DERIVED, typename VALUE> class interface {
        void do_something(VALUE v) {
            static_cast<DERIVED<VALUE>*>(this)->do_something(v);
        }
    };
    
    template <typename VALUE> class derived : public interface<derived, VALUE> {
        void do_something(VALUE v) { ... }
    };
    
    typedef interface<derived, int> derived_t;
    

    Note that you are eliminating directly providing the other template parameter(s) to the derived template; the "interface" still receives them.

    This also lets you build up typedefs in the "interface" that depend on the type parameters, which will be accessible from the derived template.

    The above typedef doesn't work because you can't typedef to an unspecified template. This works, however (and C++11 has native support for template typedefs):

    template <typename VALUE>
    struct derived_interface_type {
        typedef typename interface<derived, VALUE> type;
    };
    
    typedef typename derived_interface_type<int>::type derived_t;
    

    You need one derived_interface_type for each instantiation of the derived template unfortunately, unless there's another trick I haven't learned yet.

    0 讨论(0)
  • 2020-11-22 03:59

    Here's one generalized from something I just used. I'm posting it since it's a very simple example, and it demonstrates a practical use case along with default arguments:

    #include <vector>
    
    template <class T> class Alloc final { /*...*/ };
    
    template <template <class T> class allocator=Alloc> class MyClass final {
      public:
        std::vector<short,allocator<short>> field0;
        std::vector<float,allocator<float>> field1;
    };
    
    0 讨论(0)
  • 2020-11-22 04:01

    In the solution with variadic templates provided by pfalcon, I found it difficult to actually specialize the ostream operator for std::map due to the greedy nature of the variadic specialization. Here's a slight revision which worked for me:

    #include <iostream>
    #include <vector>
    #include <deque>
    #include <list>
    #include <map>
    
    namespace containerdisplay
    {
      template<typename T, template<class,class...> class C, class... Args>
      std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
      {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        for (auto const& obj : objs)
          os << obj << ' ';
        return os;
      }  
    }
    
    template< typename K, typename V>
    std::ostream& operator << ( std::ostream& os, 
                    const std::map< K, V > & objs )
    {  
    
      std::cout << __PRETTY_FUNCTION__ << '\n';
      for( auto& obj : objs )
      {    
        os << obj.first << ": " << obj.second << std::endl;
      }
    
      return os;
    }
    
    
    int main()
    {
    
      {
        using namespace containerdisplay;
        std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
        std::cout << vf << '\n';
    
        std::list<char> lc { 'a', 'b', 'c', 'd' };
        std::cout << lc << '\n';
    
        std::deque<int> di { 1, 2, 3, 4 };
        std::cout << di << '\n';
      }
    
      std::map< std::string, std::string > m1 
      {
          { "foo", "bar" },
          { "baz", "boo" }
      };
    
      std::cout << m1 << std::endl;
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-11-22 04:02

    This is what I ran into:

    template<class A>
    class B
    {
      A& a;
    };
    
    template<class B>
    class A
    {
      B b;
    };
    
    class AInstance : A<B<A<B<A<B<A<B<... (oh oh)>>>>>>>>
    {
    
    };
    

    Can be solved to:

    template<class A>
    class B
    {
      A& a;
    };
    
    template< template<class> class B>
    class A
    {
      B<A> b;
    };
    
    class AInstance : A<B> //happy
    {
    
    };
    

    or (working code):

    template<class A>
    class B
    {
    public:
        A* a;
        int GetInt() { return a->dummy; }
    };
    
    template< template<class> class B>
    class A
    {
    public:
        A() : dummy(3) { b.a = this; }
        B<A> b;
        int dummy;
    };
    
    class AInstance : public A<B> //happy
    {
    public:
        void Print() { std::cout << b.GetInt(); }
    };
    
    int main()
    {
        std::cout << "hello";
        AInstance test;
        test.Print();
    }
    
    0 讨论(0)
提交回复
热议问题