Search code examples
c++c++17parameter-packdeduction-guide

Why does the class deduction guide fail when using a typedef?


In a piece of the code I currently write I make use of a class deduction guide. You can find the excerpt of the code stripped down to a simple (but meaningless example) below: I've got a class User, which derives its first template parameter from the constructor's first argument, and the second one from the size of the parameter pack provided as second argument:

#include <cstddef>
#include <type_traits>

/// First constructor parameter, which can be used in order to derive the boolean
template <int, bool switcher> struct P1 {};

/// Class which depends on the boolean flag (from first parameter) and the amount of elements provided.
template <bool switcher, size_t amountKids> struct User {
  template <int p1, int... pn> explicit constexpr User(P1<p1, switcher> &child,
      P1<pn, switcher>... parents) noexcept {}
};

/// Deduction guide
template <bool f, int p1, int... pn> User(P1<p1, f> &child, P1<pn, f> ...) ->User<f, sizeof...(pn) + 1>;

int main() {
  P1<1, true> child;
  User sa{child, P1<1, true>{}};
  User sa2{child, child, child};
}

This works fine (compiles). When I make a tiny modification by replacing the parameter pack's type by a type that depends on the template parameter switcher however, the deduction fails:

#include <cstddef>
#include <type_traits>

/// First constructor parameter, which can be used in order to derive the boolean
template <int, bool switcher> struct P1 {};

/// In the real example, conditional_type holds a different type, depending on the #bool
template <bool, typename T> struct conditional_type { using type = T; };
template <bool switcher, typename T> using conditional_type_t = typename conditional_type<switcher, T>::type;

template <bool switcher, size_t amountKids> struct User {
  template <int p1, int... pn> explicit constexpr User(P1<p1, switcher> &child,
      conditional_type_t<switcher, P1<pn, switcher>>... parents) noexcept {}
};

template <bool f, int p1, int... pn> User(P1<p1, f> &child, conditional_type_t<f, P1<pn, f>>...) ->User<f, sizeof...(pn) + 1>;

int main() {
  conditional_type_t<true, P1<1, true>> child2;
  P1<1, true> child;
  static_assert(std::is_same_v<decltype(child), decltype(child2)>);

  User sa{child, P1<1, true>{}}; //< fails: 2 arguments provided, expecting one, can't derive amountKids
  User sa2{child, child, child}; //< fails: 
}

Why is that?

Both variants of the code can be found here.


Solution

  • Your deduction guide in the second example is equivalent to what we get substituting the alias:

    template <bool f, int p1, int... pn>
    User(P1<p1, f> &child, typename conditional_type<f, P1<pn, f>>::type ...)
        -> User<f, sizeof...(pn) + 1>;
    

    In any syntax typename A::B where A is a dependent type, the type A is a non-deduced context. Since pn only appears in a non-deduced context, it can never be deduced, so that deduction guide can never be used.

    For a similar reason, the constructor of User can never be used with more than one argument, even if template arguments for User are explicitly specified.