Search code examples
c++templatestype-traits

template type_trait function, accept container of a given type and/or derived type


in my work we do a lot of pair programming, and i wrote a function that ONLY accepts containers of a SINGLE type or its derivates, but my co-worker is afraid it will fail code review because it looks so damn ugly and says there's gotta be a better way:

here is the signature, the Fruit class is a base class that i renamed Fruit just for this thread:

template <class Container>
typename enable_if<is_base_of<Fruit, typename remove_pointer<typename Container::value_type>::type>::value, void>::type
processFruits(container& fruits)
{
    //process the elements and change them if needed. thats why no const
}

what it does is: returns void and enables the function IF its a container AND the type inside the container is a Fruit and/or derivided of fruit. i also used std::remove_pointer because i needed to know the "type" of the pointer (the container will most likely have pointers).

this compiles and works as intended, but as i said i don't know it its the best way to do it, it seems too verbose and might get cut of on code review.

EDIT: this also accepts templated classes, don't have to be containers. is there a way i can limit it to only accept STL containers?

any alternate ideas or is it fine like it is? thanks in advance.


Solution

  • It is a bit horrible to read.

    Well for starters you don't need to say enable_if<B, void> you can just say enable_if<B> and use the default template argument.

    You can easily split it into separate pieces:

    template <class T>
      struct is_fruity
      : is_base_of<Fruit, T>
      { };
    
    template <class Container, typename Value = typename Container::value_type>
      struct is_fruit_container
      : is_fruity<typename remove_pointer<Value>::type>>
      { };
    
    template<class Container>
      typename enable_if<is_fruit_container<Container>::value>::type
      processFruits(Container& fruits)
      {
        //process the elements and change them if needed. thats why no const
      }
    

    If you have a compiler supporting alias templates you can make it even easier to read:

    template<typename Cond>
      using Require = typename enable_if<Cond::value>::type;
    
    template<class Container>
      Require<is_fruit_container<Container>>
      processFruits(Container& fruits)
      {
        //process the elements and change them if needed. thats why no const
      }
    

    this also accepts templated classes, don't have to be containers. is there a way i can limit it to only accept STL containers?

    I'm not sure what you mean by "templated classes", it only accepts types with a nested value_type type which is a type derived from Fruit or a pointer to such type, it doesn't have to be a template. To limit it to "STL containers" you need to write a trait to indentify an "STL container", however you want to define that. To do it properly you'd need a trait that tests for begin(), end(), size() members, as well as all the nested types specified by the container requirements, iterator, value_type, etc.

    template<typename C, typename Chead, typename... Ctail>
    struct static_and
    {
      static const bool value = C::value && static_and<Chead, Ctail...>::value;
    };
    
    template<typename C>
    struct static_and<C>
    {
      static const bool value = C::value;
    };
    
    template<typename C>
    struct is_container
    : static_and<has_begin<C>, has_end<C>, has_iterator<C>, has_value_type<C> /* etc */>
    { };