I was reading the paper regarding template argument deduction for class templates here http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r3.html. This feature is in the C++17 standard, and some things had confused me.
template <typename T>
class Something {
public:
// delete the copy and move constructors for simplicity
Something(const Something&) = delete;
Something(Something&&) = delete;
explicit Something(T&&) { ... }
explicit Something(const T&) { ... }
template <typename U, typename EnableIfNotT<U, T>* = nullptr>
Something(U&&) { ... }
};
Given the above code, if someone tries to instantiate an instance of the above template like this
auto something = Something{std::shared_ptr<int>{}};
will the rvalue reference overload always be called? Since the overload set considered for deduction is
template <typename T>
Something<T> F(T&&) { ... }
template <typename T>
Something<T> F(const T&) { ... }
template <typename T, typename U, typename EnableIfNotT<U, T>*>
Something<T> F(U&&) { ... }
T
parameter, is this the intended behavior?
- The second overload will never be preferred over the first one (since that is now a forwarding reference overload, instead of being an rvalue reference overload), so what is supposed to happen here?
No, it is not a forwarding reference. This is a key distinction. From [temp.deduct.call]:
A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])).
Your candidates are:
template <typename T>
Something<T> F(T&&); // this ONLY matches non-const rvalues
template <typename T>
Something<T> F(const T&); // this matches everything
template <typename T, typename U, typename EnableIfNotT<U, T>*>
Something<T> F(U&&); // this matches nothing
When you write:
auto something = Something{std::shared_ptr<int>{}};
The T&&
constructor is preferred, with T=std::shared_ptr<int>
, so you end up with Something<std::shared_ptr<int>>
as your class template specialization. If had instead written:
std::shared_ptr<int> p;
auto something = Something{p};
then the T const&
constructor is preferred (indeed it is the only viable candidate). Although we end up in the same place: Something<std::shared_ptr<int>>
.
- And it seems like the last one can never be called without explicitly specifying the T parameter, is this the intended behavior?
Correct, T
is a non-deduced context. This makes sense - this constructor exists to do conversions, but you need to specify what you're converting to in order to do the conversion. It would never make sense to have this "just work" for you.
- Further are user defined deduction guides required to be after the class definition?
Yes. That's just where they go, by rule. It doesn't make sense to have trailing return type in the constructor - the constructor doesn't "return" anything.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With