SU3
SU3

Reputation: 5387

Specializing constructors with requires for a possibly reference type

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

Answers (1)

Barry
Barry

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

Related Questions