I'm creating an implementation of std::optional
in C 14. However, I'm slightly confused with how the move constructor is specified. Here's what I'm referring to (emphasis mine):
The expression inside
noexcept
is equivalent tois_nothrow_move_constructible_v<T>
. This constructor shall not participate in overload resolution unlessis_move_constructible_v<T>
istrue
. Ifis_trivially_move_constructible_v<T>
istrue
, this constructor shall be a constexpr constructor.
What does it mean to remove the move constructor from overload resolution? Deletion and SFINAE don't seem to work for this scenario.
CodePudding user response:
You define a base class template and specialize it for different T
constructibility characteristics.
For example see libstdc .
CodePudding user response:
It means it is not defined and the class cannot be move constructed. More interesting question is why is it needed?
I am not 100% sure I have the right answer to that.
First, the constraint does not break anything. The constructor cannot be implemented if T
cannot be move constructed.
Second, all methods of std::optional
are very tricky due to std::optional<std::optional<T>>
issue which could easily lead to ambiguous calls if proper constraints are not taken, optional(U&& value)
is really susceptible to this.
The main reason is, I believe, because we want optional<T>
to act as T
whenever possible and there is one edge case, that I am aware of, when the existence of std::optional
move constructor for non-moveable T
leads to unnecessary compiler errors. Coincidentally, it is the case of returning std::optional
by value from functions, something I do very often.
For a variable x
of type T
, return x
in a function T foo()
calls move constructor if it accessible, copy if not.
Take these simple definitions:
#include <utility>
struct CopyOnly {
CopyOnly() = default;
CopyOnly(const CopyOnly &) = default;
CopyOnly(CopyOnly &&) = delete;
CopyOnly &operator=(const CopyOnly &) = default;
CopyOnly &operator=(CopyOnly &&) = delete;
~CopyOnly() = default;
};
template <typename T> struct Opt {
Opt() = default;
Opt(const Opt &other) : m_value(other.m_value) {}
Opt(Opt &&other) : m_value(std::move(other.m_value)) {
// Ordinary move ctor.
// Same as =default, just writing for clarity.
}
// Ignore how `T` is actually store to be "optional".
T m_value;
};
and this example
template <typename T> T foo(const T &t) {
auto x = t;
return x;
}
int main() {
Opt<int> opt_int;
CopyOnly copy;
Opt<CopyOnly> opt_copy;
foo(opt_int);//#1
foo(copy);//#2
foo(opt_copy);//#3
}
return x
:
- Calls move constructor because
opt_int
can be moved. - Calls copy constructor as a fallback because it cannot be moved.
- Compiler error because
Opt<CopyOnly>
has accessible move constructor so it is chosen, but its instantiation leads to an error duem_value(std::move(other.m_value))
trying to explicitly calls deleted move ctor.
If one disables the move constructor, copy constructor is chosen and the code is identical to #2.
Yes, SFINAE does not work for constructors, use base classes forcing the compiler to do the right thing.
CodePudding user response:
It means it doesn't exist in c 14. In c 20 you can do this with requires clauses.
You can get fancy with default and inherited bases etc; if you inherit from a class without a move ctor, you don't have one. Things that don't exist don't participate in overload resolution.