Search code examples
c++templatespolymorphism

Is constraining template parameters bad practice?


In C++, it is legal to assume a template parameter has certain fields or methods according to this source. In case it does not, it will simply fail to compile.

template<class Container>
void draw_all(Container& c)
{
    for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
}

In this example, Container has to implement begin() and end() in order for the program to compile. However, looking at the prototype of the function, no such restriction seems apparent.

In other words, the information on the usage of the interface becomes intertwined with the implementation (even when using static_assert).

This to me seems like bad practice (although I myself encountered situations where it still was the most elegant solution I could find). Is it, or if not, what would be the justification for using this?


Solution

  • Until C++20, this is all we had. The one thing you cannot take away from C++ templates pre C++20 is that they allowed for great flexibility (which can be needed in some cases), and in a way allowed for an implicit contract between the API provider and the API consumer.

    C++20 came along and introduced concepts almost literally to fix the issues caused by how loose said contracts were.

    Nowadays, that function would likely be written as:

    template <class Container>
    concept DrawableContainer = requires (Container c) {
      { c.begin() };
      { c.end() };
      { *(c.begin()) } -> std::convertible_to<typename Container::value_type>;
      requires std::invocable<decltype(&Shape::draw), decltype(*(c.begin()))>;
      // Plus whatever else constraints are needed
    };
    
    void draw_all(DrawableContainer auto& c)
    {
        for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
    }
    

    This still allows the same flexibility, but has the added feature of being better documented even with just code.

    If you don't have access to concepts, there's no better solution than a good old template. You can try to use SFINAE everywhere to make it a bit easier to use.