Search code examples
templatesc++17instantiation

How to conditionally code a template function/class based on instantiation traits?


The example I'm providing is artificial and senseless but it is a reduction of a more complex (and more logic) code.

Code

template<typename T>
void MyTemplate(T t) 
{
    wstringstream stringStream;
    stringStream << t << endl;

    // rest of the code
}

struct MyClass {};

void func()
{
    MyTemplate(1); // OK
    MyTemplate(MyClass()); // error C2679: binary '<<': no operator found which takes a right-hand operand of type 'T' (or there is no acceptable conversion)
}

Problem

MyTemplate is generic but cannot compile with any instantiation.

Question

Is there a way to code MyTemplate in the following manner:

void MyTemplate(T t) 
{
    wstringstream stringStream;

    // if/directives/magic to check if object is stream-able do
        stringStream << t << endl;
    // else
        stringStream << "object is not stream-able" << endl;

    // rest of the code
}

Solution

  • Yes. First we need a trait to check if a given type is streamable to a given stream:

    template<typename T, typename Stream, typename = void>
    struct can_stream : std::false_type{};
    
    template<typename T, typename Stream>
    struct can_stream<T, Stream, std::void_t<decltype(std::declval<Stream>() << std::declval<T>())>> : std::true_type{};
    

    Having this in our toolbox, we can use if constexpr in our function:

    template<typename T>
    void MyTemplate(T t) 
    {
        wstringstream stringStream;
    
        if constexpr(can_stream<T, std::wstringstream>::value)
        { 
            stringStream << t << endl;
        }
        else
        {
            stringStream << "object is not stream-able" << endl;
        }
    }
    

    And thats it. It is good practice to provide a shorthand for the traitvalue:

    template<typename T, typename Stream>
    inline constexpr auto can_stream_v = can_stream<T, Stream>::value;
    

    So we have our final working example:

    #include <iostream>
    #include <sstream>
    #include <type_traits>
    
    using namespace std;
    
    template<typename T, typename Stream, typename = void>
    struct can_stream : std::false_type{};
    
    template<typename T, typename Stream>
    struct can_stream<T, Stream, std::void_t<decltype(std::declval<Stream>() << std::declval<T>())>> : std::true_type{};
    
    template<typename T, typename Stream>
    inline constexpr auto can_stream_v = can_stream<T, Stream>::value;
    
    template<typename T>
    void MyTemplate(T t) 
    {
        wstringstream stringStream;
    
        if constexpr(can_stream_v<T, std::wstringstream>)
        { 
            stringStream << t << endl;
        }
        else
        {
            stringStream << "object is not stream-able" << endl;
        }
    
        std::wcout << stringStream.str();
    }
    
    struct MyClass {};
    
    int main()
    {
        MyTemplate(1);
        MyTemplate(MyClass()); 
        return 0;
    }