3

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.

Zizheng Tai
  • 6,170
  • 28
  • 79
  • The "this works" part doesn't really work for gcc: "template argument deduction/substitution failed: t.C:48:6: note: cannot convert ‘v’ (type ‘std::vector’) to type ‘std::vector&&’" -- so you're already behind the 8-ball, coming out of the gate. – Sam Varshavchik Apr 07 '16 at 12:44
  • @SamVarshavchik I'm using `-std=c++11 -Wall -Wextra -pedantic-errors`, and both g++ 4.8/4.9/5.3 and clang++ shipped with Xcode 7.3 can compile it (on OS X). Maybe you were not using C++11? – Zizheng Tai Apr 07 '16 at 12:49
  • If your comment is directed at me, I'm using gcc 5.3.1 in c++11 mode. Newer versions of gcc are much more strict when it comes to enforcing C++. Additionally: the Monad specialization takes a `std::vector` as a parameter. That doesn't match up with what Ftype...::type results in. I don't think this will work. – Sam Varshavchik Apr 07 '16 at 12:52
  • In order to make the "this works" version to compile, I have to pass std::move(v) as a parameter to foo. Which makes sense. – Sam Varshavchik Apr 07 '16 at 12:55
  • @SamVarshavchik Ah I see, that was actually a typo, sorry about that! I'll correct `foo`'s signature. Regarding your additional comment, `Ftype::type` itself is not a full type, it's also a template type. Why would it not work? – Zizheng Tai Apr 07 '16 at 12:57
  • I believe because for the "this works" case, F is, explicitly, as a `std::vector`. For the non working case, you resolve F as, basically `template class std::vector`. The two are not the same. – Sam Varshavchik Apr 07 '16 at 12:58
  • Remember that `std::vector` has more than one parameter - you're just allowing the 2nd to be defaulted. This should probably affect `FType::type` – Useless Apr 07 '16 at 14:11
  • @Useless I've updated `FType`, but it still doesn't work. See update above. – Zizheng Tai Apr 07 '16 at 14:27

1 Answers1

2

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();
}
Barry
  • 286,269
  • 29
  • 621
  • 977