10

When derived class using base class constructor the deduction seems always fail. However, when the base class have lots of constructors it is very clumsy to re-define all the constructors. It is also a pain when base class are quickly evolved with new constructors. The old question was asked more than 2 years ago, so I wonder: is there any work around for this in 2020 when c++17 and c++2a are available?

template<typename ...As>
class base_t
{
public:
    base_t(As... args){}
};

template<typename ...As>
class A_t: public base_t<As...>
{
public:
    A_t(As... args): base_t<As...>{args...} {};
};

template<typename ...As>
class B_t: public base_t<As...>
{
    using base_t<As...>::base_t;
};

int main()
{
    base_t a{1, 2.0f};
    A_t{1, 2.0f};
    B_t{1, 2.0f}; //fails unless explicitly specialize the template
    return 0;
}

updates according to @Sam and @Barry:

The deduction guide is very helpful. However, for a little be more complicate situation, it still runs out of control:

template <typename A>
struct D_t {
    A x;
    D_t(A x) :x{x} {}
};
template<typename A, typename B>
class base2_t
{
public:
    base2_t(A a, B b){std::cout << "1\n";}
    base2_t(A a, D_t<B> c, int x){std::cout << "2\n";}
    base2_t(A a, B b, int x){std::cout << "3\n";}
    base2_t(A a, B b, int x, float y){std::cout << "4\n";}
    explicit base2_t(A(*fp)(B)){std::cout << "5\n";}
    // if we have lots of similar things like above
    // we will quickly end up write lots of different
    // guides.
};
template<typename A, typename B>
class C_t: public base2_t<A, B>
{
    using base2_t<A, B>::base2_t;
};
template<typename A, typename B, typename ...As>
C_t(A, B, As...)->C_t<A, B>;
template<typename A, typename B>
C_t(A(*)(B))->C_t<A, B>;
float func1(int x)
{
    return x;
}
int main()
{
    C_t{1, 2.0f, 3};
    base2_t{1, D_t{2.0f}, 3};
    C_t{1, D_t{2.0f}, 3}; // this is wrong, we have to deal with it by checking types and write different guides.
    base2_t{&func1};
    C_t{&func1};
}

till 2023, the proposal is accepted in c++23 P2582R1 But neither gcc nor llvm has implemented it yet. But there is a hope that in the middle of this year this issue will finally get fixed.

Wang
  • 7,250
  • 4
  • 35
  • 66

4 Answers4

5

Being able to inherit deduction guides from base classes was proposed for c++20. However, this feature didn't make it in, as the last line says:

The wording for CTAD from inherited constructors was not finalized in time for the C++20 committee draft, and will be published in a separate wording paper at a later point in time.

So as of now, you will need to provide deduction guides for the derived class explicitly (or define the constructor as you did for A_t). Hopefully, this will be fixed in c++23.

cigien
  • 57,834
  • 11
  • 73
  • 112
3

Unfortunately, there's no generic way to inherit the base class' deduction guides in C++20. There was a proposal for it (P1021), but it didn't make it for C++20.


Without that proposal, it's tempting to suggest something like:

template <typename... Args>
Derived(Args&&... args) -> mp_rename<decltype(Base((Args&&)args...)), Derived>;

That is, figure out the type that you get from passing all the args into Base using class template argument deduction there - and then just rename whatever Base<Ts...> you get into Derived<Ts...>. But the grammar for a deduction-guide is:

explicit-specifier opt template-name ( parameter-declaration-clause ) -> simple-template-id ;

And there's the added restriction in [temp.deduct.guide]/3 that:

The template-name shall be the same identifier as the template-name of the simple-template-id.

So a deduction guide for Derived has to end up with exactly -> Derived<Args...>. There's no way to stick any cleverness in there.


Without direct language help, and without the ability to do any template metaprogramming here, you're left to just carefully write the explicit deduction guides yourself. Thankfully, at least here you can write static_asserts to make sure you did the job right:

template <typename... Ts>
inline constexpr bool matches = std::is_same_v<
    decltype(Base(std::declval<Ts>()...)),
    mp_rename<decltype(Derived(std::declval<Ts>()...)), Base>>;

static_assert(matches<int>); // or whatever is reasonable for your specific types
Barry
  • 286,269
  • 29
  • 621
  • 977
  • thanks. But seems if things are more complicate than the example, I will end up writing lots of deduction guides for each derived class. If I have lots of them, it is still not much better than re-define the constructors. – Wang Apr 19 '20 at 21:53
  • @Wang If all you want to do is inherit, implementing a bunch of deduction guides is going to be less work than the constructors (especially since if the base class _has_ deduction guides, then you _have_ to implement deduction guides - at which point might as well do everything together). – Barry Apr 19 '20 at 22:45
2

One option is an explicit deduction guide (C++17):

template<typename ...As>
class base_t
{
public:
    base_t(As... args){}
};

template<typename ...As>
class B_t: public base_t<As...>
{
    using base_t<As...>::base_t;
};

template<typename ...As>
B_t( As...) -> B_t<As...>;

int main()
{
    base_t a{1, 2.0f};
    B_t b{1, 2.0f};

    B_t<int, float> bb=b;

    return 0;
}

More work will probably be needed to correctly deal with references, probably by throwing in a std::remove_reference_cv (remove_cvref_t for C++20), or a std::decay_t somewhere in there...

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
2

You could use @Barry's rename Idea with a layer of indirection:

template<class... Args>
struct ctad_tag{};

template<class... Args>
struct deriv : base<Args...> {
    using base<Args...>::base;
};

template<class... Args>
struct deriv<ctad_tag<Args...>> : deriv<Args...> {
    using deriv<Args...>::deriv;
};

template<class... Args>
deriv(Args&&... args) 
-> deriv<rename_t<decltype(base(std::forward<Args>(args)...)), ctad_tag>>;

But it's a little hacky, since you'll end up with a deriv<ctad_tag<...>> instead of a deriv<...>. Still, depending on the use case this might be fine, so I'll leave this here.

Working example: https://godbolt.org/z/ygWpJU

Velocirobtor
  • 188
  • 7