#include <utility>
template <class... Types>
class my_tuple {
public:
// constexpr my_tuple() {}
// explicit my_tuple(const Types&...){}
template <class... UTypes> explicit my_tuple(UTypes&&...u){}
template <class U1, class U2>
my_tuple(const std::pair<U1, U2>& u) {}
};
// User-Defined Deduction Guides
// template<class... UTypes>
// my_tuple(UTypes...) -> my_tuple<UTypes...>; // #1
// template<class T1, class T2>
// my_tuple(std::pair<T1, T2>) -> my_tuple<T1, T2>; // #2
int main(){
int i = 1;
double d = 4.5;
auto mtp1 = my_tuple(i, d); // CTAD #1
const auto p = std::pair(i, d);
auto mtp2 = my_tuple(p); // CTAD #2
}
The class my_tuple
tries to resemble the constructors of std::tuple
. Having "enabled" only the two constructors as in the above code, CTAD works. Thus, I interpreted the implicitly-generated deduction guides are used in this case. If I uncomment (i.e., enable) one/any of the other two constructors, it still works. However, if I enable both constructors (that is, four in total), compilation fails both with GCC and CLANG (multiple overloads instantiate to the same signature
). At this point, if I create (uncomment) the user-defined deduction guides (resembling the ones defined in the library for std::tuple
), everything works again. I also tried implementing all the corresponding constructors as in std::tuple
, and the behavior is similar.
My question is why the implicitly-generated deduction guides seem to work in the initial state of the program, but afterwards (when everything is enabled) the user-defined deduction guides are needed. My goal is to understand if the mtp1
and mtp2
object constructions (i.e., lines marked with CTAD #1
and CTAD #2
) need the user-defined deduction guides or if they actually used the implicitly-generated ones.
According to CPPreference, the deduction guides from the library (corresponding to my user-defined here) are provided just to account for the edge cases missed by the implicit deduction guides, in particular, non-copyable arguments and array to pointer conversion.
And the two examples from my code do not seem to me to fit in these edge cases.
Note: If I'm not mistaken, these are the implicitly-generated deduction guides from the constructors (plus the additional copy deduction candidate [over.match.class.deduct]
, paragraph 1.3) in the original state of my example code:
template<class... UTypes>
my_tuple(UTypes&&...) -> my_tuple<UTypes&&...>; // #1
template<class U1, class U2>
my_tuple(const std::pair<U1, U2>&) -> my_tuple<const std::pair<U1, U2>&>; // #2
template <typename T>
my_tuple(my_tuple<T>) -> my_tuple<T> // #3
Interesting links:
You might check the resulting types, it would surprise you Demo
my_tuple(i, d)
results in my_tuple<>
.
And once extra constructors un-commented, you have two default constructors for my_tuple<>
constexpr my_tuple() {}
explicit my_tuple(const Types&...){} /* with empty pack Types */
resulting in error.
With CTAD activated, you no longer generate problematic my_tuple<>
(which is still problematic BTW).