1

If you have a class template such as this:

    template <typename T, unsigned CAPACITY>
    class Collection
    {
        T m_array[CAPACITY]{};
        T m_dummy{};
        unsigned m_size{};
    }
    public:
        void display(std::ostream& ostr = std::cout) const 
        {
            ostr << "----------------------" << std::endl;
            ostr << "| Collection Content |" << std::endl;
            ostr << "----------------------" << std::endl;
        }

And I wanted to create specialization depending on the type used, but not the CAPACITY, is this possible?

I have this, which works:

    void Collection<Pair, 50u>::display(std::ostream& ostr) const
    {
        ostr << "----------------------" << std::endl;
        ostr << "| This is a Pair |" << std::endl;
        ostr << "----------------------" << std::endl;
    }

When it is called as: Collection<Pair, 50> colDictionary;

But this only works if the type is Pair, as well as the exact CAPACITY is 50.

This is what I had in mind, allowing for type to be Pair and CAPACITY to be anything:

    void Collection<Pair>::display(std::ostream& ostr) const
    {
        ostr << "----------------------" << std::endl;
        ostr << "| This is a Pair |" << std::endl;
        ostr << "----------------------" << std::endl;
    }

But this causes a "too few arguments for class template" error.

Any way to do this without changing the actual class template itself?

  • 3
    You could use [Partial template specialization](https://en.cppreference.com/w/cpp/language/partial_specialization). Check [this answer](https://stackoverflow.com/questions/15374841/c-template-partial-specialization-member-function) too. – Ch3steR Jun 05 '22 at 17:04
  • 2
    Check this [tag dispatch](https://stackoverflow.com/a/37531906/12416453) answer too. – Ch3steR Jun 05 '22 at 17:14

3 Answers3

3

It's called a partial template specialization:

template <class T, unsigned Capacity>
struct Collection {

};

template <unsigned Capacity>
struct Collection<Pair, Capacity> {
  // Specialize
};

One thing to note is that you cannot partially specialize a single function. You have to specialize the whole class template, which is irritating if the class template is long. Another quick-and-dirty way of doing this if you want to specialize a single function would be to just use a "compile-time if":

#include <type_traits>

template <class T, unsigned Capacity>
struct Collection {
  void display() const {
    if constexpr (std::is_same_v<T, Pair>) {
      // pair implementation
    } else {
      // general implementation
    }
  }
};

Or, as a more clean solution, try moving the whole thing out of the class and add a simple overload:

// Free-standing overloads:

template <class T, unsigned Capacity>
void diplay(Collection<T, Capacity> const& c) { /* ... */ }

template <unsigned Capacity>
void display(Collection<Pair, Capacity> const& c) { /* ... */ }


// The member function delegates the work to
// the overloaded functions. No template specialization
// is involved:

template <class T, unsigned Capacity>
struct Capacity {
  void display() const {
    display(*this); // calls the correct overload.
  }
};
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • Does this mean that I need to write the entire class definition twice? – OldSchool Ty Jun 05 '22 at 17:20
  • 1
    @OldSchoolTy I added edits to address your question. – Aykhan Hagverdili Jun 05 '22 at 17:24
  • @OldSchoolTy it's also possible to make member function a template and overload it with use of SFINAE. Though now `if constexpr` is often a more preferable and clean. If you need to partially specialize a free-standing function template, it is possible to turn it into a class of functional object with `operator()` so i can be used as a function. And see generic lambda https://stackoverflow.com/questions/17233547/how-does-generic-lambda-work-in-c14 – Swift - Friday Pie Jun 06 '22 at 07:44
  • 1
    @Swift-FridayPie instead of specializing `operator()` in a struct (callable object), you might be better off overloading it so it participates in overload resolution. Function template specializations are rarely desirable. – Aykhan Hagverdili Jun 06 '22 at 07:48
  • @AyxanHaqverdili actually that's what most Standard Library implementations do with certain components. Overload resolution sometimes might be problematic because it can be ambiguous if parameters are compatible. – Swift - Friday Pie Jun 06 '22 at 07:53
  • @Swift-FridayPie could you give examples from the standard library? – Aykhan Hagverdili Jun 06 '22 at 07:53
  • Harder to pin down today without looking at source of particular implementation (I don't have ability right now) because many that once were documented as such on cppreference,com are now converted with functions with if-constexpr even it's not necessary implemented in compiler that way (there are implementation performance issues sometimes related to overloading). But I think, "binder" returned by `std::bind` still is, even uses a trait class approach? `invoke` used to be a functional class. – Swift - Friday Pie Jun 06 '22 at 08:02
  • 1
    @Swift-FridayPie I remember a push towards replacing function template specializations with function overloads in the standard, but I might be misremembering something. – Aykhan Hagverdili Jun 06 '22 at 08:08
  • that should be true, the new language features in standard essentially are toolkit for that. implementations are always behind for technical reasons. THere is also a matter of corporate coding standards and platform limitations, albait that's whole other can of fish. It is useful to know all possible options. Some platforms are stuck in c++11 or 14 and never would advance because of their nature. c++20 essentially made many avionic and nav-systems applications noncompliant – Swift - Friday Pie Jun 06 '22 at 08:14
1

It seems difficult to do a partial specification.

Some ways are helpful for you to achieve it:

  1. Define a specification class for this type situation.
  2. If you just only desire to custom this behavior(but not too much and it's limited), you can use if-constexpr branches.
  3. If you want to avoid a class-partial-template (because it's a burden to rewrite all codes), then use a global function template is helpful.

Some suggested codes are given:

#include <iostream> 

template <typename T, unsigned capacity> 
class Collection {
    public: 
    void display(std::ostream &ostr = std::cout) const; 
}; 

template <typename T, unsigned c> 
void Collection<T, c>::display(std::ostream &ostr) const {
    if constexpr (c == 50u) {
        ostr << "Specification! \n"; 
    } else {
        ostr << "Normal Realization. \n"; 
    }
}

int main() {
    Collection<int, 50> c; 
    c.display(); 
}
CutieDeng
  • 31
  • 4
1

If you need to specialize a certain member function, you can use the Curiously Recurring Template Pattern (or CRTP for short). You'd then create a base class and a specialization of that base class. Both contain only the specific member function that you want to specialize.

template <class T, class CRTP, unsigned Capacity>
struct display_impl {
    void display() const {
        auto& This = static_cast<const CRTP&>(*this);
        // Use `This` to access members of Collection
    }
};

template <class CRTP, unsigned Capacity>
struct display_impl<Pair, CRTP, Capacity> {
    void display() const {
        auto& This = static_cast<const CRTP&>(*this);
        // Use `This` to access members of Collection
    }
};

Collection will now inherit from display_impl and supply itself as a template parameter:

template <class T, unsigned Capacity>
struct Collection : display_impl<T, Collection<T, Capacity>, Capacity> {
    friend struct display_impl<T, Collection<T, Capacity>, Capacity>;
};

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108