Search code examples
c++templatesvariant

Templatized child not accepted as parent when using variant


I'm trying to execute a very simple abstract factory with templatized arguments, and am receiving an error I can't get to the bottom of.

The core issue is that, outside of a template, a variant type accepts any of the members of its union, but inside of a template, this deduction seems to be failing. I can't find the bug in my construction that's leading to this occurring.

The minimal producing code is as follows; just by removing templates, it compiles just fine

class A{};
class B{};

using AB = std::variant<A,B>;

template <class T1, class T2>
class Parent
{
};

class Child : public Parent<A,B>
{
public:
    static Parent<A,B>* build(){ return new Child(); }
};


template <class T1, class T2>
using Fn = std::function<Parent<T1,T2> *(void)>;

using ParentFn = Fn<AB,AB>;

int main()
{
        // in a factory, we might say
        // map<string, ParentFn> m; m["key"] = &Child::build;
        // but for MRE I'll just do assignment
        ParentFn tmp = &Child::build; // does just fine without templates, crashes horribly with

    return 0;
}

As written, however, it spits out the compilation error

main.cpp:32:24: error: conversion from ‘Parent<A, B>* (*)()’ to non-scalar type ‘ParentFn {aka std::function<Parent<std::variant<A, B>, std::variant<A, B> >*()>}’ requested
         ParentFn tmp = &Child::build; // does just fine without templates, crashes horribly with

I believe that the issue isn't an lvalue/rvalue issue like this question, since the method being referenced is static (but we can pull it out and force it to be an lvalue without resolving this error, too). GCC is known to have some bugs with aliases but I don't see how they'd be relevant here since I think they're isolated to variadics; I'm using g++-7.1, -std=c++17

Why isn't the compiler able to accept type A where type variant<A,B> is requested? Isn't that the whole point of variants? For example, AB ab = A(); or AB ab = B(); are both valid, though that might just be clever overloading of the = operator?

Am I misusing variant, std::function, templates, aliases, or all of the above?


As proof that the problem is related to the templates, the code without templates compiles as:

class Parent
{
};

class Child : public Parent
{
public:
    static Parent* build(){ return new Child(); }
};

using Fn = std::function<Parent *(void)>;

using ParentFn = Fn;

int main()
{
        ParentFn tmp = &Child::build; // does just fine without templates, crashes horribly with

    return 0;
}

My basis for believing that std variant might be substitute for either of the individual types is the trivial test

AB ab1 = A();
ab1 = B();

both seem to be acceptable.


Solution

  • Deobfuscating your code I think what you are trying to do comes down to attempting the following.

    Works

    #include <variant>
    
    class A{};
    class B{};
    
    using AB = std::variant<A,B>;
    
    
    AB * Foo();
    
    typedef AB*(*Fn)();
    
    int main()
    {
        Fn f = &Foo;
    
    }
    

    Doesn't work

    #include <variant>
    
    class A{};
    class B{};
    
    using AB = std::variant<A,B>;
    
    
    AB * Foo();
    
    typedef A*(*Fn)();
    
    int main()
    {
        Fn f = &Foo;
    
    }
    

    error: invalid conversion from 'AB* ()()' {aka 'std::variant ()()'} to 'Fn' {aka 'A (*)()'} [-fpermissive]

    which if you want to reduce it further is attempting to convert std::variant<A,B> * to A* which will not work.

    In fact you cannot even convert A* to std::variant<A,B> *. The only conversion allowed is A to std::variant<A,B> or B to std::variant<A,B>

    https://godbolt.org/z/Xwbsou