mirror of
				https://github.com/boostorg/optional.git
				synced 2025-11-04 01:31:57 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			103 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			103 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
 | 
						|
[section Convenience Conversions and Deductions]
 | 
						|
 | 
						|
Unlike `std::optional`, `boost::optional` does not offer a number of
 | 
						|
"convenience" converting constructors, mixed relational operations and
 | 
						|
deductions for class template parameters.
 | 
						|
 | 
						|
  std::optional oi = 1;                 // OK
 | 
						|
 | 
						|
  std:string_view sv = "hi";
 | 
						|
  std::optional<std::string> os = sv;   // OK
 | 
						|
  os == sv;                             // OK
 | 
						|
 | 
						|
  std::optional<std::string> osv;
 | 
						|
  std::optional<std::string> os2 = osv; // OK
 | 
						|
  os2 == osv;                           // OK
 | 
						|
 | 
						|
They are practical, ans sometimes stem from the argument for consistency:
 | 
						|
if `(optT && *optT == u)` works then `(optT == u)` should also work.
 | 
						|
 | 
						|
However, these intelligent convenience functions sometimes produce results
 | 
						|
that are counter to the programmer intentions and produce silent bugs.
 | 
						|
 | 
						|
Consider a more complicated example:
 | 
						|
 | 
						|
  Threshold th = /*...*/;
 | 
						|
  std::optional o = th;
 | 
						|
  assert (o);
 | 
						|
 | 
						|
In this code, can we expect that thus initialized `optional` contains a value?
 | 
						|
The answer is: it depends on the type of `Threshold`. It can be defined as:
 | 
						|
 | 
						|
  using Threshold = std::optional<int>;
 | 
						|
 | 
						|
And then the assertion will fire. This is because in this case the intelligence
 | 
						|
decides that since we already have an optional, the additional wrapping into
 | 
						|
a yet another optional is unnecessary.
 | 
						|
 | 
						|
If we explicitly specify the template type, the situation doesn't get less
 | 
						|
complicated.
 | 
						|
 | 
						|
  Threshold th;
 | 
						|
  std::optional<Threshold> o = th;
 | 
						|
  assert(o);
 | 
						|
 | 
						|
Can this assertion fire? Now we have two competing constructors:
 | 
						|
 | 
						|
  template <typename U>
 | 
						|
  optional(U const&);
 | 
						|
 | 
						|
  template <typename U>
 | 
						|
  optional(optional<U> const&);
 | 
						|
 | 
						|
Which one will get chosen? Actually, we are lucky, and it is going to be the
 | 
						|
first one due to concept tricks. But let's try a different example:
 | 
						|
 | 
						|
  Threshold th;
 | 
						|
  std::optional<Threshold> o = th;
 | 
						|
  assert(o);
 | 
						|
  assert(o == th);
 | 
						|
 | 
						|
Here, the first assertion passes, but the second one fires. This is because
 | 
						|
there are two competing overloads of the comparison operator:
 | 
						|
 | 
						|
  template <typename T, typename U>
 | 
						|
  bool operator==(optional<T> const&, U const&);
 | 
						|
 | 
						|
  template <typename T, typename U>
 | 
						|
  bool operator==(optional<T> const&, optional<U> const&);
 | 
						|
 | 
						|
And this time there is no concept trickery, so the second overload is chosen,
 | 
						|
and gives different results: we are comparing an optional object `th`, which does
 | 
						|
not contain a value, with an optional object `o` which does contain a value.
 | 
						|
 | 
						|
This problem -- that the operations compile, but have runtime behavior counter
 | 
						|
to programmer's intuition -- gains new significance with the introduction of
 | 
						|
concepts to C++.
 | 
						|
 | 
						|
  static_assert(std::equality_comparable_with<std::optional<Threshold>, Threshold>);
 | 
						|
 | 
						|
Concepts have both syntactic constraints and semantic constraints. Syntactic
 | 
						|
constraints are statically checked by the compiler. For semantic constraints,
 | 
						|
functions that use the concept trust the programmer that these constraints are
 | 
						|
met, and if not, this is __UB__.
 | 
						|
 | 
						|
These are problems with `std::optional`. `boost::optional` doesn't have these
 | 
						|
problems, because it does not offer the said convenience operations.
 | 
						|
 | 
						|
The design principle for `boost::optional` is not to offer functionality that
 | 
						|
nicely deduces the programmer intentions in 95% of the cases, and in the remaining
 | 
						|
5% renders effects counter to programmer expectations.
 | 
						|
 | 
						|
Instead, this library recommends using a more verbose syntax that works in 100%
 | 
						|
of the cases:
 | 
						|
 | 
						|
  Threshold th;
 | 
						|
  auto o = boost::make_potional(th);   // *always* add a new layer of optionality
 | 
						|
 | 
						|
  return boost::equal_pointees(o, th); // *always* unpack optionals for comparison
 | 
						|
  return o && *o == th;    // *always* treat the right-hand side argument as value
 | 
						|
 | 
						|
[endsect]
 |