Reputation: 5387
I need to make a wrapper class template that can possibly contain a reference member. I have both copy and move constructors defined, where references of the wrapped type are passed as arguments. This is all good if the type is not a reference. But if it is, both type&
and type&&
become lvalue references, and the two constructors conflict with each other.
I tried defining one set of constructors for the case where the type is not a reference and another for when it is, making use of the requires
clause, as shown in the code below. But this doesn't compile if the type is a reference! GCC somehow treats the poisoned constructors as still valid.
Am I misunderstanding how requires
works, or is this a bug? Here is the same example in the compiler explorer: https://godbolt.org/z/qjMxx3nGE
#include <type_traits>
template <typename T>
struct S {
using type = T;
type x;
S(const type& x) requires(!std::is_reference_v<type>)
: x(x) { }
S(type&& x) requires(!std::is_reference_v<type>)
: x(x) { }
S(type x) requires(std::is_reference_v<type>)
: x(x) { }
};
int main(int argc, char** argv) {
int i = 5;
S<int&> s(i);
return s.x;
}
Upvotes: 3
Views: 1690
Reputation: 303087
Every function you define has to be distinct in some way - different name, different parameters, different constraints, etc.
For T=int&
, your three constructors are:
S(int&) requires (!std::is_reference_v<int&>);
S(int&) requires (!std::is_reference_v<int&>);
S(int&) requires (std::is_reference_v<int&>);
The first two are the same, but there are two of them. That's an error. You need to differentiate these cases.
requires
doesn't prevent the function from existing in the way that you might think - it just removes it as a candidate from overload resolution. It doesn't behave like a hypothetical if
would:
if (std::is_reference_v<T>) {
S(T x);
} else {
S(T const&);
S(T&&);
}
It'd be easier to simply add a single constructor template that's constrained on being constructible:
template <typename U> requires std::constructible_from<T, U>
S(U&& u) : x(std::forward<U>(u)) { }
Or provide a specialization for S<T&>
and split the constructors that way.
Upvotes: 4