Zizheng Tai
Zizheng Tai

Reputation: 6646

Implicit instantiation of template specialization

I'm designing a class template Monad, which is parameterized by a template type. For example, there can be Monad<std::vector>, Monad<std::shared_ptr>, etc.

I have this utility struct that can be used to extract the container type of a higher-kinded type (e.g. extract std::vector from a std::vector<int>):

template<typename>
struct FType;

template<typename A>
struct FType<std::vector<A>> {
    template<typename B>
    using type = std::vector<B>;
};

// FType<std::vector<int>>::type == std::vector

Now for Monad:

template<template <typename...> class F>
struct Monad;

template<>
struct Monad<std::vector> {

    /*
     * Example parameters:
     *   FA: std::vector<int>
     *   F:  std::vector
     *   M:  Monad<std::vector>
     */
    template<typename FA,
             template <typename...> class F = FType<FA>::template type,
             typename M = Monad<F>>
    static void foo(const FA &)
    {
        M::bark();
    }

    static void bark()
    {
        std::cout << "Woof!\n";
    }
};

I'm trying to have Monad::foo automatically infer its template parameters. So instead of doing:

std::vector<int> v{1, 2, 3};

// This works
Monad<std::vector>::foo<
    std::vector<int>,   // FA
    std::vector,        // F
    Monad<std::vector>  // M
>(v);

// This also works
Monad<std::vector>::foo<
    std::vector<int>,   // FA
    std::vector         // F
>(v);

I want to be able to do:

std::vector<int> v{1, 2, 3};

// This does not compile
Monad<std::vector>::foo(v);

// Neither does this
Monad<std::vector>::foo<
    std::vector<int>  // FA
>(v);

The error I got (for both not-compiling examples) was:

error: implicit instantiation of undefined template 'Monad<type>'
        M::bark();
        ^

template is declared here
struct Monad;
       ^

error: incomplete definition of type 'Monad<type>'
        M::bark();
        ~^~

How can I solve this?

UPDATE: As Sam has pointed out, std::vector, FType<std::vector<Something>>::type, and FType<std::vector<SomethingElse>>::type are all different! (Although for a specific A, std::is_same shows that std::vector<A>, FType<std::vector<Something>>::type<A>, and FType<std::vector<SomethingElse>>::type<A> are the same.) If I specialize Monad<FType<std::vector<int>>::type>, then things work out...but clearly this is not desirable... Is there any other way to implement FType?

UPDATE 2: Changing FType to this:

template<typename... A>
struct FType<std::vector<A...>> {
    template<typename... B>
    using type = std::vector<B...>;
};

has no effect.

Upvotes: 3

Views: 683

Answers (1)

Barry
Barry

Reputation: 304122

Why it doesn't work

The problem is when you explicitly provide std::vector for F, F is actually std::vector. But when you allow it to be deduced, it gets deduced as FType<A>::type - which is not std::vector. It's deducible as such, but it isn't the same.

How to fix it

You don't actually... need any of this extra machinery for this particular problem right? You can just deduce the argument to foo as an instance of a template template:

template <template <typename...> class F, typename... Args>
static void foo(F<Args...> const& ) {
    using M = Monad<F>; 
    M::bark();
}

More generally, you can just go from FA to the monad in a single step:

template <class FA>
struct as_monad;

template <class FA>
using as_monad_t = typename as_monad<FA>::type;

template <template <typename...> class F, typename... Args>
struct as_monad<F<Args...>> {
    using type = Monad<F>;
};

And then:

template <class FA>
static void foo(FA const& ) {
    using M = as_monad_t<FA>; 
    M::bark();
}

Upvotes: 2

Related Questions