简体   繁体   中英

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.

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!
}

As state by the compiler O is not deducible from const typename potato<O>::baked& (on left side of :: ).

You have several workarounds:

  • Move baked outside parent and make it template:

     // Possibly in namespace details template <typename T> struct baked_impl { template <class O> baked_impl(const typename baked_impl<O>& p) : something(static_cast<T>(p.something)) { } baked_impl() = default; T something; }; template <class T> struct potato { using baked = baked_impl<T>; // ... }; 
  • Add parent info in baked and use SFINAE:

     template <class T> struct potato; // traits for SFINAE template <class T> struct is_potato : std::false_type {}; template <class T> struct is_potato<potato<T>> : std::true_type {}; template <class T> struct potato { using value_type = T; struct baked { using parent = potato; template <class O, std::enable_if_t<is_potato<typename O::parent>::value, int> = 0> baked(const O& p) : something(static_cast<typename O::parent::value_type>(p.something)) { } baked() = default; T something; }; // ... }; 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM