Search code examples
c++templatesc++14template-templates

Passing specified template type as template parameter


Say I have some template type...

template <typename T> struct Foo {
    Foo(T t) {}
};

Is there a way to pass a specified Foo type to a function so that the function has direct visibility of T?

Ideally I would be able to write something like this...

Foo<int> foo = create<Foo<int>>();

The closest I've been able to come is

template <
    template <typename> typename TT,
    typename T,
    std::enable_if_t<std::is_same<TT<T>, Foo<T>>::value, int> = 0
>
Foo<T> create() {
    return Foo<T>(T());
}

which would then be used like

Foo<int> foo = create<Foo, int>();

Thanks for any help.


Solution

  • This form of template template parameter is only allowed in C++17:

    template < //           v---------- typename here not allowed
        template <typename> typename TT,
        typename T,
        std::enable_if_t<std::is_same<TT<T>, Foo<T>>::value, int> = 0
    >
    Foo<T> create() {
        return Foo<T>(T());
    }
    

    You must replace the typename pointed out by class:

    template < //           v---------- class allowed
        template <typename> class TT,
        typename T,
        std::enable_if_t<std::is_same<TT<T>, Foo<T>>::value, int> = 0
    >
    Foo<T> create() {
        return Foo<T>(T());
    }
    

    In C++17, both compiles and are equivalent.


    To make your syntax Foo<int> foo = create<Foo<int>>(); work, you simply need to do this:

    template <typename T>
    T create() {
        return T{};
    }
    

    If you want to limit what type can be sent, you must create a type trait:

    // default case has no typedef
    template<typename>
    struct first_param {};
    
    // when a template is sent, define the typedef `type` to be equal to T
    template<template<typename> class TT, typename T>
    struct first_param<TT<T>> {
        using type = T;
    };
    
    // template alias to omit `typename` everywhere we want to use the trait.
    template<typename T>
    using first_param_t = typename first_param<T>::type;
    

    Then, use your trait:

    template <
        typename T, 
        void_t<first_param_t<T>>* = nullptr
    > //       ^---- if the typedef is not defined, it's a subtitution error.
    T create() {
        return T(first_param_t<T>{});
    }
    

    You can implement void_t like this:

    template<typename...>
    using void_t = void;
    

    Live at Coliru