I'm creating an implementation of C++17's std::optional<class T> in C++14. The specification states that the move constructor should be excluded from overload resolution if T is not move-constructible, and made trivial if T is trivially move-constructible. I'm stuck on getting the former.
Here's a code sample of what I have so far:
template<class T, bool = is_trivially_move_constructible_v<T>>
class optional_move_construct : public optional_copy_construct<T> {
public:
optional_move_construct(optional_move_construct&& rhs) :
optional_base<T>() {
if (rhs.m_full) {
_impl_construct(std::move(rhs.m_value));
}
}
};
optional_copy_construct<T> is part of the inheritance chain I'm using, so I won't worry about it.
There are two ways I can "remove" the move constructor, neither of which works for this scenario.
Option 1: Delete the move constructor. This won't work because deleted functions are included in overload resolution.
Option 2: Use SFINAE to exclude the move constructor from overload resolution. This won't work either, because SFINAE requires a template function to work, and that would be lower-priority than the default move constructor.
How would I go about doing this?
A move constructor has one T&& argument, and possibly additional arguments provided those have default values. That means you can add std::enable_if_t<Condition, int> = 0 as an additional argument to your move constructor.
The compiler won't create a one-argument optional::optional(T&&) move constructor when you have that two-argument move constructor.
Related
I'm playing with c++20 concepts
https://godbolt.org/z/931xaeY45
#include <type_traits>
template<typename T>
class Optional
{
public:
~Optional() requires (!std::is_trivially_destructible_v<T>);
};
#include <cstdio>
int main()
{
const char* bool_value[] = {"false", "true"};
bool is_trivial = std::is_trivial_v<Optional<int>>;
bool is_trivial_destructable = std::is_trivially_destructible_v<Optional<int>>;
std::printf("std::is_trivial_v<Optional<int>> = %s\n", bool_value[is_trivial]);
std::printf("std::is_trivial_destructable<Optional<int>> = %s\n", bool_value[is_trivial_destructable]);
}
Output:
std::is_trivial_v<Optional<int>> = false
std::is_trivial_destructable<Optional<int>> = false
But, I expected that the destructor doesn't get instantiated, because for this situation requires (false) is generated for template parameter int.
Q: Why does ~Optional() requires (false) make class Optional non-trivial?
My read of the standard, and MSVC, says that this is a compile error.
In C++20, this:
~Optional() requires (!std::is_trivially_destructible_v<T>);
is a "prospective destructor". It may or may not become the actual destructor based on a series of rules. But what really matters is this rule:
If a class has no user-declared prospective destructor, a prospective destructor is implicitly declared as defaulted ([dcl.fct.def]).
Your class has a "user-declared prospective destructor". Therefore, there is no "implicitly declared as defaulted" constructor ever.
That's important because this rule kicks in:
At the end of the definition of a class, overload resolution is performed among the prospective destructors declared in that class with an empty argument list to select the destructor for the class, also known as the selected destructor.
The program is ill-formed if overload resolution fails.
Since T is trivially destructible, overload resolution can't call your prospective desturctor, since the requires constraint kills it. And there are no other destructors to find. So it isn't supposed to compile, and GCC may simply not yet implement the rule correctly.
Regardless, you need to provide a defaulted alternative destructor:
~Optional() requires (!std::is_trivially_destructible_v<T>);
~Optional() = default;
From my understanding you have to supply an alternative for trivially destructible types T with default (seems to work in GCC)
template<typename T>
class Optional {
public:
// Chosen for not trivially destructible types
~Optional() requires (not std::is_trivially_destructible_v<T>);
// Chosen for everything else (trivially destructible types)
~Optional() = default;
};
In your case there is only one option given that is not set to default which makes it not default destructible.
In certain applications I am stuck with a c++98 compiler.
I like the idea to handle certain errors returning an optional, so I crafted this trivial class to handle them:
template<class T> class Optional
{
public:
Optional<T>() : i_Assigned(false) {}
explicit Optional<T>(const T& v) : i_Value(v), i_Assigned(true) {}
bool assigned() const { return i_Assigned; }
const T& value() const { if(!i_Assigned) throw std::runtime_error("Optional value not assigned!"); return i_Value; }
Optional<T>& operator=(const T& v) { i_Value=v; i_Assigned=true; return *this; }
private:
T i_Value;
bool i_Assigned;
};
I'm satisfied with my tests so far and I'm about to use it in my production code:
Optional<double> val = funct_returning_optional();
if( val.assigned() ) use(val.value());
else something_went_wrong();
...But its extreme simplicity makes me wonder, what I'm missing? Why the standard implementation is so complex? What could possibly go wrong by adopting my self made abomination?
One basic issue with your implementation is that it isn't able to accommodate types that lack a default constructor. Similarly, if the default constructor of T has any side effects, those side effects will occur when Optional<T> is constructed. The std::optional<T> is not supposed to have any side effects when default-constructed (in fact, the default constructor is constexpr).
The way std::optional handles this is to not have a member of type T, but instead a member that is a union, one of whose elements has type T:
template <class T>
class optional {
union Storage {
T t;
char c;
Storage() : c(0) {}
} storage;
bool has_value;
public:
optional() : has_value(false) {}
};
When the optional object is default-constructed, the storage.c member is initialized, and there is no T object yet, so no constructor needs to be called for T.
This immediately complicates the rest of the implementation. Whenever the optional goes from having a T to not having a T (which can happen when it is destroyed, reset, or assigned from an empty optional), the destructor must be explicitly called on storage.t. Whenever it goes from not having a T to having a T (which can happen when it is copy constructed from a non-empty optional, assigned from a T, or assigned from a non-empty optional), placement new must be used to start the lifetime of storage.t.
The C++17 optional also has certain other key requirements. I assume you already know about move constructors and move assignment operators. What really complicates things, though, is the requirement for std::optional<T> to have a trivial copy constructor, copy assignment operator, move constructor, move assignment operator, or destructor whenever the corresponding member of T is trivial. In C++17, the only way to implement this requirement is to have optional<T> pull in its special member functions from internal base classes, where one version of the base class handles the trivial case by using the implicitly defined function, and another version handles the nontrivial case with a user-provided function. This is the main reason why the standard library implementations are so long.
std::optional<Class> a;
a.value_or( Class() ).GetMember();
Why cant we do this like:
a.value_or().GetMember();
Can standard specialize value_or on a default constructible basis?
Unfortunately this value_or doesn't have this option and I agree with with Max Langhof's comment that it should be a separate function value_or_default.
Nevertheless, as Eljay pointed out in this comment, the code from the question might cause problems for classes with expensive default construction. In cases when the optional does not carry a value, we don't want the default constructor to be called.
I've put together a little workaround for that particular case:
struct default_constructor_t {
explicit default_constructor_t() = default;
};
inline constexpr default_constructor_t default_constructor{};
class Class {
...
Class(default_constructor_t) : Class{} {}
...
};
int GetMember(std::optional<Class> Object) {
return Object.value_or(default_constructor).GetMember();
}
In this solution, you need to add an additional implicit constructor to your class. This solution is also lazy. It will call default constructor only if optional doesn't have a value.
UPD:
After a bit of thinking, I believe that I came up with a bit more generic solution that will work even with types the developer can't modify. And even if one has this ability, less code is better, right?
struct DefaultConstructed {
template <class T> operator T() const {
return T();
}
};
constexpr DefaultConstructed Default;
class Class {
...
};
int GetMember(std::optional<Class> Object) {
return Object.value_or(Default).GetMember();
}
This solution uses conversion operator instead of implicit construction. Special object named Default can be converted (so to speak) to any type by calling this type's default constructor. It also retains the nice property of the original solution to be lazy.
I have the following class:
template <typename T>
class A
{
public:
A(const T& value): m_value(value)
{
}
template <typename M>
A(const A<M>& other): A(T(other.m_value))
{
}
private:
T m_value;
};
Should this class follow the Rule of Five? Or template <typename T>template <typename M>
A<T>::A(const A<M>& other) is not considered a copy constructor?
The rule of five exists because if you have a copy constructor then that's probably because you can't use the automatically-generated one, which in turn is probably because you have some indirection that needs to be taken care of. When that's so, you usually also need to take care of it during assignment and destruction.
What you have is not a copy constructor. It converts from another type. There is no indication in your code that the member m_value needs special handling during assignment or destruction. Furthermore, the copy-constructor that you do have performs no special operations.
If you can think of some code that you need to put in said special member functions, then go ahead and do it. Otherwise, you don't need them.
General rules can be useful but you do ultimately still need to make design decisions based on thinking! You don't implement functions just because a rule of thumb said that you may need to.
According to the C++ Standard (15.8.1 Copy/move constructors)
1 A non-template constructor for class X is a copy constructor if
its first parameter is of type X&, const X&, volatile X& or const
volatile X&, and either there are no other parameters or else all
other parameters have default arguments
So this
template <typename M>
A(const A<M>& other): A(T(other.m_value))
{
}
is not a copy constructor. It is a conversion constructor.
I do not see here the relation with the Rule of Five.
I'm seeing some different behavior between g++ and msvc around value initializing non-copyable objects. Consider a class that is non-copyable:
class noncopyable_base
{
public:
noncopyable_base() {}
private:
noncopyable_base(const noncopyable_base &);
noncopyable_base &operator=(const noncopyable_base &);
};
class noncopyable : private noncopyable_base
{
public:
noncopyable() : x_(0) {}
noncopyable(int x) : x_(x) {}
private:
int x_;
};
and a template that uses value initialization so that the value will get a known value even when the type is POD:
template <class T>
void doit()
{
T t = T();
...
}
and trying to use those together:
doit<noncopyable>();
This works fine on msvc as of VC++ 9.0 but fails on every version of g++ I tested this with (including version 4.5.0) because the copy constructor is private.
Two questions:
Which behavior is standards compliant?
Any suggestion of how to work around this in gcc (and to be clear, changing that to T t; is not an acceptable solution as this breaks POD types).
P.S. I see the same problem with boost::noncopyable.
The behavior you're seeing in MSVC is an extension, though it's documented as such in a roundabout way on the following page (emphasis mine) http://msdn.microsoft.com/en-us/library/0yw5843c.aspx:
The equal-sign initialization syntax is different from the function-style syntax, even though the generated code is identical in most cases. The difference is that when the equal-sign syntax is used, the compiler has to behave as if the following sequence of events were taking place:
Creating a temporary object of the same type as the object being initialized.
Copying the temporary object to the object.
The constructor must be accessible before the compiler can perform these steps. Even though the compiler can eliminate the temporary creation and copy steps in most cases, an inaccessible copy constructor causes equal-sign initialization to fail (under /Za, /Ze (Disable Language Extensions)).
See Ben Voigt's answer for a workaround which is a simplified version of boost::value_initialized, as pointed out by litb in a comment to Ben's answer. The docs for boost::value_initalized has a great discussion of the problem, the workaround, and some of the pitfalls of various compiler issues.
I don't think template metaprogamming is needed. Try
template <class T>
void doit()
{
struct initer { T t; initer() : t() {} } inited;
T& t = inited.t;
...
}
There's §12.8/14:
A program is ill-formed if the copy constructor or the copy assignment operator for an object is implicitly used and the special member function is not accessible.
And then there's §12.8/15:
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor and/or destructor for the object have side effects.
So, the question is really, if the implementation omits the call to the copy constructor (which it is clearly allowed to do), is the copy constructor actually used?
And, the answer to that is yes, per §3.2/2:
A copy constructor is used even if the call is actually elided by the implementation.
Have you seen what happens when you compile using /Wall with MSVC? It states the following about your class:
nocopy.cc(21) : warning C4625: 'noncopyable' : copy constructor could not be
generated because a base class copy constructor is inaccessible
nocopy.cc(21) : warning C4626: 'noncopyable' : assignment operator could not be
generated because a base class assignment operator is inaccessible
GCC remedy:
create a copy constructor for noncopyable (and an assignment operator ideally!) that does what it can to copy the information from noncopyable_base, namely invoking the constructor for noncopyable_base that has no parameters (since that is the only one accessible by noncopyable) and then copying any data from noncopyable_base. Given the definition of noncopyable_base, however, it seems there is no data to copy, so the simple addition of noncopyable_base() to the initializer list of a new noncopyable(const noncopyable &) function should work.
Take note of what MSVC said about your program though. Also note that if you use T t() rather than T t = T(), another warning (C4930) is generated by MSVC, though GCC happily accepts it either way without any warning issued.