Search code examples
c++c++11variadic-templatesstatic-assertenable-if

Enable template only for specific templated class


This question is inspired from my previous question No template parameter deduction of parameter pack.

Consider following code example:

#include <memory>
#include <string>

template<typename... FArgs>
class Callback
{
    public:
    class Handle{};
};

class BaseCallbackHandle
{
};

using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;

template<typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle( H handle)
{
   return {};
}

int main()
{
    Callback<int>::Handle h;
    std::string s;
    makeTypeErasedCallbackHandle(h); //should compile fine
    makeTypeErasedCallbackHandle(s); //should raise a compile error
}

See also http://coliru.stacked-crooked.com/a/5f2a2e816eef6afd

The function template makeTypeErasedCallbackHandle now takes any class as input parameter. Is there any way to ensure (e.g. with static-assert or enable_if), that only Callback<FArgs...>::Handle (with any FArgs) is allowed as H? The example with Callback<int>::Handle shall compile, while std::string shall fail.


Solution

  • Define a type within your Handle class, and refer to that type inside makeTypeErasedCallbackHandle():

    #include <memory>
    #include <string>
    
    template <typename... FArgs>
    struct Callback {
        struct Handle {
            using callback_type = Callback<FArgs...>;    
        };
    };
    
    struct BaseCallbackHandle {
    };
    
    using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;
    
    template <typename H>
    TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H handle) {
        using callback_type = typename H::callback_type;
    
        return {};
    }
    
    int main() {
        Callback<int>::Handle h;
        std::string s;
        makeTypeErasedCallbackHandle(h); //should compile fine
        makeTypeErasedCallbackHandle(s); //should raise a compile error
    }
    

    Live example

    This will fail during instantiation for any H that doesn't define the nested type.


    With a little more effort, you can static_assert to produce a meaningful message to the client, while at the same time increasing the flexibility of the solution via type traits. This has the advantage that callback_impl::is_callback can be specialised for arbitrary handle types:

    #include <memory>
    #include <string>
    
    namespace callback_impl {
    
    struct callback_identification_type {};
    
    template <typename T, typename = void>
    struct is_callback : std::false_type {};
    
    template <typename T>
    struct is_callback<T,
        std::enable_if_t<std::is_same<typename T::callback_id_type,
                         callback_identification_type>::value>>
     : std::true_type {};
    
    }
    
    template <typename... FArgs>
    struct Callback {
        struct Handle {
            using callback_id_type = callback_impl::callback_identification_type;    
        };
    };
    
    struct BaseCallbackHandle {
    };
    
    using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;
    
    template <typename H>
    TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H handle) {
        static_assert(callback_impl::is_callback<H>::value,
                      "The handle type is not a member of a recognised Callback<T...>");
        return {};
    }
    
    int main() {
        Callback<int>::Handle h;
        std::string s;
        makeTypeErasedCallbackHandle(h); //should compile fine
        makeTypeErasedCallbackHandle(s); //should raise a compile error
    
        return 0;
    }
    

    Live example

    Output:

    g++ -std=c++14 -O2 -Wall -Wno-unused-local-typedefs -pedantic -pthread main.cpp && ./a.out
    main.cpp: In instantiation of 'TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H) [with H = std::__cxx11::basic_string<char>; TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>]':
    main.cpp:41:35:   required from here
    main.cpp:32:5: error: static assertion failed: The handle type is not a member of a recognised Callback<T...>
         static_assert(callback_impl::is_callback<H>::value,
         ^~~~~~~~~~~~~