Search code examples
c++c++11constructorinner-classestemplate-argument-deduction

Parent template argument deduction in nested class constructor


I am trying to write the "promotion" constructor of a nested class that can deduce the parent class template. It works fine for the parent class, but not in the nested class. Here is a code example.

template <class T>
struct potato {
    struct baked {
        template <class O>
        baked(const typename potato<O>::baked& p)
                : something(static_cast<T>(p.something)) {
        }
        baked() = default;

        T something;
    };

    template <class O>
    potato(const potato<O>& p)
            : mybaked(p.mybaked) {
    }
    potato() = default;

    baked mybaked;
};

int main(int, char**) {
    potato<int> potato1;
    potato<short> potato2(potato1);
}

Is this legal?

Various compilers output various errors. Clang has the most readable in my mind. It states :

candidate template ignored: couldn't infer template argument 'O'

https://godbolt.org/z/y_IZiE

So I'm guessing either I've messed up the signature, or this isn't a c++ supported feature.


Solution

  • I don't know of any way to deduce the template argument T for a baked's parent potato<T>. You can know T using decltype(p.something) but that doesn't seem to help solve the problem with calling the constructor. One workaround is to change baked's constructor to take any O and assume it has a something :

    struct baked {
        template <class O>
        baked(const O & p) : something(static_cast<T>(p.something))
        { }
    
        baked() = default;
    
        T something;
    };
    

    This will work but it is less type-safe than your original code seems to intend. One workaround for that problem could be to introduce a static_assert that checks that O is actually a potato<U>::baked :

    #include <type_traits>
    
    template <class T>
    struct potato {
        struct baked {
            template <class O>
            baked(const O & p) : something(static_cast<T>(p.something))
            {
                using t_parent = potato<decltype(p.something)>;
                static_assert(std::is_same<O, typename t_parent::baked>::value, "Not a baked potato!");
            }
    
            baked() = default;
    
            T something;
        };
    
        template <class O>
        potato(const potato<O>& p)
            : mybaked(p.mybaked) {
        }
        potato() = default;
    
        baked mybaked;
    };
    

    This should compile fine for the intended usage but fail with "Not a baked potato!" if you try to pass anything else with a something. This would fail :

    struct foo {
        int something = 0;
    };
    
    
    int main(int, char**) {
        foo bar;
        potato<int>::baked baz(bar); // Error: Not a baked potato!
    }