C++ Template argument deduction vs specialization

If you don’t know the type at compile time you can just use the template parameter type and the compiler will deduce the type from the function call itself. For example, see the commented out c’tor below. This is great for typing the size of std::array function parameter. However you could come unstuck if a user was to pass in a type that you didn’t anticipate.

If you need to enforce the API usage to a specific group of types – so that users won’t accidentally pass in the wrong type – then full specialization is a better option. See use of Widget objects below. Now if you pass in the wrong type you get a compiler (for explicit) or linker (for deduced) error.

#include <iostream>
#include <memory>

struct WidgetA {};
struct WidgetB {};
struct WidgetC {};

template<typename T>
class Test
{
public:
    Test(T input);
    T m_value;
};

// // Using template argument deduction
// template<typename T>
// Test<T>::Test(T input)
// : m_value(input)
// {

// }

// explicit specialization
template<>
Test<WidgetA>::Test(WidgetA input)
{
    std::cout << "WidgetA";
}

// explicit specialization
template<>
Test<WidgetB>::Test(WidgetB input)
{
    std::cout << "WidgetB";
}

int main()
{
    WidgetA wa;
    WidgetB wb;
    WidgetC wc;

    Test<WidgetB> t_explicit_a(wb);
    Test<WidgetA> t_explicit_b(wa);
    Test t_deduced_b(wb);

    // error: no matching function for call to 'Test<WidgetB>::Test(WidgetC&)'
    // Test<WidgetB> t_explicit_bad(wc);   

    // undefined reference to `Test<WidgetC>::Test(WidgetC)'
    // Test t_deduced_bad(wc);

    return 0;
}

live example

The anoyance with templates is that all implementations must go in the header file. However, the explicit specializations of the function can be put in a source file. For example theDiskioHardwareMMC<DiskioProtocolSPI>::initialize() function below, should be placed in a source file.

Note, it you want a generic c’tor with a common intialisor for all specializations then a non-specialized version must go in the template header along with the template class itself. (see derived HW template class c'tor)

#include <iostream>

// abstract HW class
class DiskioHardwareBase {
public:
    using DSTATUS = short;
    virtual DSTATUS initialize() = 0;

};

// protocol device
class DiskioProtocolSPI 
{
public:
    int a{0};
};

// derived HW template class 
template<typename DISKIO_PROTOCOL>
class DiskioHardwareMMC : public DiskioHardwareBase
{
public:
    DiskioHardwareMMC(DISKIO_PROTOCOL periph_interface);
    DSTATUS initialize();
    DISKIO_PROTOCOL m_protocol;
private:

};
// derived HW template class c'tor
template<typename DISKIO_PROTOCOL>
DiskioHardwareMMC<DISKIO_PROTOCOL>::DiskioHardwareMMC(DISKIO_PROTOCOL periph_interface)
: m_protocol(periph_interface)
{
    std::cout << "Calling class template constructor - DiskioHardwareMMC<DISKIO_PROTOCOL>" << std::endl;
}


template<>
DiskioHardwareBase::DSTATUS DiskioHardwareMMC<DiskioProtocolSPI>::initialize()
{
    std::cout << "Calling explicitly-specialized member function - DiskioHardwareMMC<DiskioProtocolSPI>" << std::endl;
    std::cout << "m_protocol.a = " << m_protocol.a;
    return 0;
}


int main()
{
    DiskioProtocolSPI spi;
    spi.a = 5;
    DiskioHardwareMMC<DiskioProtocolSPI> mmc_spi(spi);
    mmc_spi.initialize();
    return 0;
}

live example

Categories: C++

Leave a Reply

Your email address will not be published. Required fields are marked *