C++ / a working model

29 / 163   ·   C++11   ·   8 min

Why Member Templates Cannot Be virtual

Keep this sentence

Member function templates cannot be declared virtual, and a specialization of one will not automatically override a base-class virtual function. Ordinary non-template members of a class template may nevertheless be virtual. When both a generic entry point and run-time extension are required, the template conversion layer can be separated from a virtual interface of fixed signature.

In this lesson
  1. Distinguish Two Different Template Positions
  2. The Restriction Is a Language Rule, Not a Vtable Capacity Deduction
  3. Generic Adapter Layer Plus Fixed Dynamic Interface
  4. Example
  5. Exercise

Distinguish Two Different Template Positions

template<class T> void send(T) is a member function template; the standard forbids adding virtual to it. Even if send(int) is instantiated, that template specialization will not override a base-class virtual function merely because the signatures happen to match. To override, a genuine non-template member must be declared, for example void send(int) override.

On the other hand, in template<class T> struct Sink { virtual void send(T) = 0; } send itself has no template parameter list; it is an ordinary member of a class template and therefore may be virtual. After Sink<int> is instantiated the virtual interface signature is already fixed as int; Sink<double> is a different class type.

The Restriction Is a Language Rule, Not a Vtable Capacity Deduction

Templates produce specializations according to use; virtual functions establish overriding relationships for an already determined class interface. Their times of expansion and matching rules differ. A fixed signature lets independently compiled callers and implementers agree on the same dynamic interface; common ABI vtable layouts are also organized around such interfaces.

“There could be infinitely many templates so the vtable cannot hold them” can serve as motivational intuition, but it is not a proof, and one must not claim on that basis that a compiler theoretically cannot implement another model. An accurate answer first cites the rule that member templates may not be virtual, then explains the frequently confused legitimate case of ordinary members of class templates.

Generic Adapter Layer Plus Fixed Dynamic Interface

The example supplies a non-virtual put<T> template that only converts the input into a uniform string and then calls the fixed-signature virtual write. Callers obtain generic convenience; derived classes need implement only one dynamic interface. When conversion may fail, the error should be reported before write so that incomplete data is not handed to the implementation.

This design is a form of boundary convergence: the run-time virtual interface is not made to recognize arbitrary types; type differences stay in the compile-time adapter layer. If data cannot be converted losslessly into a common representation, limited overloads, variant, or type erasure with a well-defined set of operations may be chosen; do not lose business semantics merely to look generic.

Pitfalls

  • A derived-class member template of the same name may hide a base-class function without overriding it; a call through a base reference still dispatches according to the original virtual interface.
  • That a member is not a template does not imply that its enclosing class is not a template; whether virtual is legal depends on the function declaration itself.

Run an example

Minimum C++11 · complete program · Download .cpp

#include <cassert>
#include <sstream>
#include <stdexcept>
#include <string>

class Sink {
public:
    template<class T>
    void put(const T& value) {
        std::ostringstream stream;
        stream << value;
        if (!stream) throw std::runtime_error("conversion failed");
        write(stream.str());
    }
    virtual ~Sink() = default;
private:
    virtual void write(const std::string& text) = 0;
};

class Buffer final : public Sink {
    std::string data_;
    void write(const std::string& text) override { data_ += text; }
public:
    const std::string& data() const { return data_; }
};

int main() {
    Buffer buffer;
    Sink& sink = buffer;
    sink.put(12);
    sink.put(" apples");
    assert(buffer.data() == "12 apples");
}

Compile locally

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread objects-virtual-template.cpp -o example && ./example

Expected result

Expected: exit 0, no output; every assert holds.

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

The base class declares virtual void f(int); the derived class only writes template<class T> void f(T). Where does a call f(1) through Base& go? How can it be forwarded to the template?

Show a reference answer

The member template does not override the base-class function, so the virtual call still reaches the final overrider in the base; if the base function is pure virtual the derived class remains abstract. A non-template void f(int value) override { f<int>(value); } should be added. Explicit template arguments can select the template specialization and avoid the wrapper calling itself infinitely.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog