I am trying to make a typed compare function that do some customized comparison for different types.
#include <type_traits>
template <typename T>
bool typedCompare(const T& lhs, const T& rhs)
{
return lhs == rhs; // default case, use ==
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return (lhs - rhs) < 1e-10;
}
int main()
{
typedCompare(1, 1);
typedCompare(1.0, 1.0);
return 0;
}
Here I have a special version for double that compare the difference with a small amount (please ignore the fact that I did not use std::abs()). I have a few other custom types that I need to do some special comparison, and I cannot change their == operator for some reason.
Besides, I still want to have a "catch-all" style function that employs the == operator. My problem is that when trying to compile this code snippet the compiler complains about that typedCompare(1.0, 1.0) is ambiguous, it can choose either of the two functions provided.
Why? And how could I resolve this issue?
Thanks.
Why?
In short, you use SFINAE incorrectly so both function templates are valid when you call typedCompare for doubles.
And how could I resolve this issue?
In this particular case, fix SFINAE to make it work correctly:
template <typename T>
typename std::enable_if<!std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return lhs == rhs; // default case, use ==
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return (lhs - rhs) < 1e-10;
}
Please note, that this solution is not that good in terms of customization for many types. Another way is to use tag dispatching:
struct floating_point_tag {};
// other tags
template <typename T>
bool typedCompare(const T& lhs, const T& rhs, floating_point_tag) {
return (lhs - rhs) < 1e-10;
}
// implementations for other tags
template <typename T>
bool typedCompare(const T& lhs, const T& rhs) {
if (std::is_floating_point<T>::value) {
return typedCompare(lhs, rhs, floating_point_tag{});
}
// other checks here
return lhs == rhs;
}
Finally, with C++17 you might make use of if constexpr:
template <typename T>
bool typedCompare(const T& lhs, const T& rhs) {
if constexpr (std::is_floating_point<T>::value) {
return (lhs - rhs) < 1e-10;
} else { // add other if-else here
return lhs == rhs;
}
}
The problem with the code in your question is that when the floating point typedCompare() is SFINAE enabled, collide with the general version because the compiler can't prefer one version over the other.
To solve this problem, I suggest you another way, based on template partial specialization (available only with structs and classes, so need a helper struct)
If you define an helper struct as follows
template <typename T, typename = void>
struct typedCompareHelper
{
static constexpr bool func (T const & lhs, T const & rhs)
{ return lhs == rhs; } // default case, use ==
};
template <typename T>
struct typedCompareHelper<T,
typename std::enable_if<std::is_floating_point<T>::value>::type>
{
static constexpr bool func (T const & lhs, T const & rhs)
{ return (lhs - rhs) < 1e-10; }
};
you avoid the ambiguity problem because the typedCompareHelper specialization in more specialized of the generic one.
You can simply add more specializations for different special cases making only attention in avoiding collisions (different specializations of the same level that apply over the same type).
Your typedCompare() become simply
template <typename T>
bool typedCompare (T const & lhs, T const & rhs)
{ return typedCompareHelper<T>::func(lhs, rhs); }
I like both the solution from #max66 with a helper template and the multiple solutions from #Edgar Rokyan.
Here is another approach that can be used for what you want using a helper template function.
#include <type_traits>
#include <iostream>
#include <string>
// elipsis version is at the bottom of the overload resolution priority.
// it will only be used if nothing else matches the overload.
void typeCompare_specialized(...)
{
std::cout << "SHOULD NEVER BE CALLED!!!\n";
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typeCompare_specialized(const T& lhs, const T& rhs)
{
std::cout << "floating-point version\n";
return (lhs - rhs) < 1e-10;
}
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
typeCompare_specialized(const T& lhs, const T& rhs)
{
std::cout << "integral version\n";
return lhs == rhs;
}
template <typename T>
auto typedCompare(const T& lhs, const T& rhs)
-> typename std::enable_if<std::is_same<bool,decltype(typeCompare_specialized(lhs, rhs))>::value,bool>::type
{
return typeCompare_specialized(lhs, rhs);
}
template <typename T>
auto typedCompare(const T& lhs, const T& rhs)
-> typename std::enable_if<!std::is_same<bool,decltype(typeCompare_specialized(lhs, rhs))>::value,bool>::type
{
std::cout << "catch-all version\n";
return lhs == rhs;
}
int main()
{
typedCompare(1, 1);
typedCompare(1.0, 1.0);
typedCompare(std::string("hello"), std::string("there"));
return 0;
}
Running the above program will yield the following output:
integral version
floating-point version
catch-all version
Again, I would prefer to use one of the previous answers mentioned. I include this possibility for completeness.
I would also like to add that you should make sure your typeCompare_specialized() template versions should not have any overlap, otherwise you could get a compiler error declaring that there are multiple candidate overloads to use.
Related
I am getting a strange, Call to function 'Equals' that is neither visible in the template definition nor found by argument-dependent lookup, for a simple tag dispatch implementation.
template <typename T>
bool Equals(T lhs, T rhs){
return Equals(rhs, lhs, conditional_t<is_floating_point<T>::value, true_type, false_type>{});
}
template <typename T> // for floating
bool Equals(T lhs, T rhs, true_type){
return abs(lhs - rhs) < 0.01;
}
template <typename T> // for all the other
bool Equals(T lhs, T rhs, false_type){
return lhs == rhs;
}
what am I doing wrong?
When performing the tag dispatching, you are not instantiating the true_type. But more importantly, you need to change the order of your functions, the tagged functions need to be defined before the function that is performing the dispatching, eg:
template <typename T> // for floating
bool Equals(T lhs, T rhs, true_type){
return abs(lhs - rhs) < 0.01;
}
template <typename T> // for all the other
bool Equals(T lhs, T rhs, false_type){
return lhs == rhs;
}
// moved down here!
template <typename T>
bool Equals(T lhs, T rhs){
return Equals(lhs, rhs, conditional_t<is_floating_point<T>::value, true_type{}, false_type>{});
}
That being said, in C++17 and later, you don't need to use tag dispatching at all, you can use if constexpr instead, eg:
template <typename T>
bool Equals(T lhs, T rhs){
if constexpr (is_floating_point_v<T>)
return abs(lhs - rhs) < 0.01;
else
return lhs == rhs;
}
I'm developing a header-only library for automatic/algorithmic differentiation. The goal is to be able to simply change the type of the variables being fed to a function and calculate first and second derivatives. For this, I've created a template class that allows the programmer to select the storage type for the private data members. Included is a snippet below with an offending operator overload.
template <typename storage_t>
class HyperDual
{
template <typename T> friend class HyperDual;
public:
template <typename T>
HyperDual<storage_t> operator+(const HyperDual<T>& rhs) const
{
HyperDual<storage_t> sum;
for (size_t i = 0; i < this->values.size(); i++)
sum.values[i] = this->values[i] + rhs.values[i];
return sum;
}
protected:
std::vector<storage_t> values;
};
Later on, to maximize the versatility, I provide template functions to allow interaction.
template <typename storage_t, typename T>
HyperDual<storage_t> operator+(const HyperDual<storage_t>& lhs, const T& rhs)
{
static_assert(std::is_arithmetic<T>::value && !(std::is_same<T, char>::value), "RHS must be numeric");
return HyperDual<storage_t>(lhs.values[0] + rhs);
}
template <typename storage_t, typename T>
HyperDual<storage_t> operator+(const T& lhs, const HyperDual<storage_t>& rhs)
{
static_assert(std::is_arithmetic<T>::value && !(std::is_same<T, char>::value), "LHS must be numeric");
return HyperDual<storage_t>(lhs + rhs.values[0]);
}
What I'm encountering is that the compiler is trying to instantiate the second non-member template function.
#include "hyperspace.h"
int main()
{
HyperDual<long double> one(1); // There is an appropriate constructor
HyperDual<double> two(2);
one + two;
return 0;
}
I get the static_assert generated error "LHS must be numeric" for this. How would I resolve the ambiguity?
use enable_if_t to make the non-member template can only be applied in the specific context?
template <typename storage_t, typename T, typename = enable_if_t<std::is_arithmetic<T>::value && !(std::is_same<T, char>::value)>>
HyperDual<storage_t> operator+(const HyperDual<storage_t>& lhs, const T& rhs)
{
static_assert(std::is_arithmetic<T>::value && !(std::is_same<T, char>::value), "RHS must be numeric");
return HyperDual<storage_t>(lhs.values[0] + rhs);
}
the static_assert may be duplicated here.
Ok. I found my own issue. It comes down to the difference between static_assert and std::enable_if
Replacing my template declaration and removing static_assert, I achieve equivalent functionality:
template <typename storage_t, typename T,
typename = typename std::enable_if<std::is_arithmetic<T>::value && !std::is_same<T, char>::value>::type>
HyperDual<storage_t> operator+(const T& lhs, const HyperDual<storage_t>& rhs)
{
return HyperDual<storage_t>(lhs + rhs.value());
}
(Small detail, but rhs.values[0] was replaced with rhs.value(). This had nothing to do with the template issue, but was related to member access.
I have a template class typically instantiated by <double>.
My header has something like:
template <typename T> class F;
// Non-member functions
template <typename T> const F<T> operator*(F<T> lhs, const F<T>& rhs);
template <typename T> const F<T> operator*(const F<T>& lhs, const T& rhs);
template <typename T>
class F
{
// blah blah
F<T>& operator*=(const F<T>& rhs);
F<T>& operator*=(const T& rhs_scalar);
// blah
friend const F<T> operator*(const F<T>& lhs, const T& rhs) { return F(lhs) *= rhs; }
friend const F<T> operator*(const T& lhs, const F<T>& rhs) { return F(rhs) *= lhs; }
};
In my .cpp file, I have something like:
#include "F.H"
// lots of template<typename T> returntype F<T>::function(args){ body }
template <typename T>
F<T>& F<T>::operator*=(const F<T>& rhs)
{
// check sizes, etc
// do multiplication
return *this;
}
template <typename T>
F<T>& F<T>::operator*=(const T& rhs_scalar)
{
for(auto &lhs : field_) // field_ is a vector holding values
{
lhs *= rhs_scalar;
}
return *this;
}
// Non-member parts
template <typename T> operator*(F<T> lhs, const F<T>& rhs)
{ lhs *= rhs; return lhs; }
template <typename T> operator*(const F<T>& lhs, const T& rhs)
{ return F<T>(lhs) *= rhs; }
template class F<double>;
template const F<double> operator*<double>(F<double>, const F<double>&);
This compiles and runs ok, and allows things like:
F<double> test(..);
test *= 2.5; test *= 10; test /= 2.5; test /= 10; // and so on
The question I have is: Can I reduce the number of declarations and definitions of my operators, whilst retaining the ability to implicitly promote an int to a double, etc? Can I rearrange the code so that the friend .. operator*(..) bodies are defined outside of the header file? (I suspect this would involve more specific instantiations in one or both of the header and the cpp file)
(Side note: how? Scott Meyer's Item 46 in 'Effective C++' describes implicit parameter conversion, but it seems like that describes allowing the construction of a temporary object to allow (in that case) Rational(int) * Rational_object_already_created;)
Can I reduce the number of declarations and definitions of my operators, whilst retaining the ability to implicitly promote an int to a double, etc?
You can do that by providing a converting constructor.
template <typename T2>
F(F<T2> const& f2 ) {...}
In my C++ code, I wrote like this:
template <typename T, typename Pred>
inline const T BestOfTwo(const T& lhs, const T& rhs, Pred p = std::less<T>())
{
return p(lhs, rhs) ? lhs : rhs;
}
But this didn't work when I called BestOfTwo(3, 5). The compiler told me that no instance of overload matched. So now I have to write it like this:
template <typename T, typename Pred = std::less<T> >
inline const T BestOfTwo(const T& lhs, const T& rhs, Pred p = Pred())
{
return p(lhs, rhs) ? lhs : rhs;
}
And this worked with no error when I called BestOfTwo(3, 5). But I think the previous style is more convenient and I didn't figure out where it went wrong. What are some suggestions?
Only the second version is correct (if you don't want to specify the Pred parameter manually), but only since C++11. There is already an answer from Angew that clarify why the first version is incorrect, without specification of the Pred parameter.
If you cannot use C++11 you should write two overloads (one with Pred and one without, that use std::less), since default template parameters for function templates are explicitly forbidden in C++98.
template<typename T, typename Pred>
inline const T BestOfTwo(const T& lhs, const T& rhs, Pred p = Pred())
{
//
}
template<typename T>
inline const T BestOfTwo(const T& lhs, const T& rhs)
{
return BestOfTwo<T, std::less<T> >(lhs, rhs);
}
The first version would work if you specified the template arguments explicitly:
BestOfTwo<int, std::less<int>>(3, 5)
The reason is that default function arguments cannot be used to deduce the type for a template parameter.
Why std::optional (std::experimental::optional in libc++ at the moment) does not have specialization for reference types (compared with boost::optional)?
I think it would be very useful option.
Is there some object with reference to maybe already existing object semantics in STL?
When n3406 (revision #2 of the proposal) was discussed, some committee members were uncomfortable with optional references. In n3527 (revision #3), the authors decided to make optional references an auxiliary proposal, to increase the chances of getting optional values approved and put into what became C++14. While optional didn't quite make it into C++14 for various other reasons, the committee did not reject optional references and is free to add optional references in the future should someone propose it.
The main problem with std::optional <T&> is — what should optRef = obj do in the following case:
optional<T&> optRef;
…;
T obj {…};
optRef = obj; // <-- here!
Variants:
Always rebind — (&optRef)->~optional(); new (&optRef) optional<T&>(obj).
Assign through — *optRef = obj (UB when !optRef before).
Bind if empty, assign through otherwise — if (optRef) {do1;} else {do2;}.
No assignment operator — compile-time error "trying to use a deleted operator".
Pros of every variant:
Always rebind (chosen by boost::optional and n1878):
Consistency between the cases when !optRef and optRef.has_value() — post-condition &*optRef == &obj is always met.
Consistency with usual optional<T> in the following aspect: for usual optional<T>, if T::operator= is defined to act as destroying and constructing (and some argue that it must be nothing more than optimization for destroying-and-constructing), opt = … de facto acts similarly like (&opt)->~optional(); new (&opt) optional<T&>(obj).
Assign through:
Consistency with pure T& in the following aspect: for pure T&, ref = … assigns through (not rebinds the ref).
Consistency with usual optional<T> in the following aspect: for usual optional<T>, when opt.has_value(), opt = … is required to assign through, not to destroy-and-construct (see template <class U> optional<T>& optional<T>::operator=(U&& v) in n3672 and on cppreference.com).
Consistency with usual optional<T> in the following aspect: both haveoperator= defined at least somehow.
Bind if empty, assign through otherwise — I see no real benefits, IMHO this variant arises only when proponents of #1 argue with proponents of #2, however formally it's even more consistent with the letter of requirements for template <class U> optional<T>& optional<T>::operator=(U&& v) (but not with the spirit, IMHO).
No assignment operator (chosen by n3406):
Consistency with pure T& in the following aspect: pure T& doesn't allow to rebind itself.
No ambiguous behavior.
See also:
Let’s Talk about std::optional<T&> and optional references.
Why Optional References Didn’t Make It In C++17.
There is indeed something that has reference to maybe existing object semantics. It is called a (const) pointer. A plain old non-owning pointer. There are three differences between references and pointers:
Pointers can be null, references can not. This is exactly the difference you want to circumvent with std::optional.
Pointers can be redirected to point to something else. Make it const, and that difference disappears as well.
References need not be dereferenced by -> or *. This is pure syntactic sugar and possible because of 1. And the pointer syntax (dereferencing and convertible to bool) is exactly what std::optional provides for accessing the value and testing its presence.
Update:
optional is a container for values. Like other containers (vector, for example) it is not designed to contain references. If you want an optional reference, use a pointer, or if you indeed need an interface with a similar syntax to std::optional, create a small (and trivial) wrapper for pointers.
Update2: As for the question why there is no such specialization: because the committee simply did opt it out. The rationale might be found somewhere in the papers. It possibly is because they considered pointers to be sufficient.
IMHO it is very okay to make std::optional<T&> available. However there is a subtle issue about templates. Template parameters can become tricky to deal with if there are references.
Just as the way we solved the problem of references in template parameters, we can use a std::reference_wrapper to circumvent the absence of std::optional<T&>. So now it becomes std::optional<std::reference_wrapper<T>>. However I recommend against this use because 1) it is way too verbose to both write the signature (trailing return type saves us a bit) and the use of it (we have to call std::reference_wrapper<T>::get() to get the real reference), and 2) most programmers have already been tortured by pointers so that it is like an instinctive reaction that when they receive a pointer they test first whether it is null so it is not quite much an issue now.
If I would hazard a guess, it would be because of this sentence in the specification of std::experimental::optional. (Section 5.2, p1)
A program that necessitates the instantiation of template optional
for a reference type, or for possibly cv-qualified types in_place_t or
nullopt_t is ill-formed.
I stumbled upon this several times and I finally decided to implement my solution that doesn't depend on boost. For reference types it disables assignment operator and doesn't allow for comparison of pointers or r-values. It is based on a similar work I did some time ago, and it uses nullptr instead of nullopt to signal absence of value. For this reason, the type is called nullable and compilation is disabled for pointer types (they have nullptr anyway). Please let me know if you find any obvious or any non-obvious problem with it.
#ifndef COMMON_NULLABLE_H
#define COMMON_NULLABLE_H
#pragma once
#include <cstddef>
#include <stdexcept>
#include <type_traits>
namespace COMMON_NAMESPACE
{
class bad_nullable_access : public std::runtime_error
{
public:
bad_nullable_access()
: std::runtime_error("nullable object doesn't have a value") { }
};
/**
* Alternative to std::optional that supports reference (but not pointer) types
*/
template <typename T, typename = std::enable_if_t<!std::is_pointer<T>::value>>
class nullable final
{
public:
nullable()
: m_hasValue(false), m_value{ } { }
nullable(T value)
: m_hasValue(true), m_value(std::move(value)) { }
nullable(std::nullptr_t)
: m_hasValue(false), m_value{ } { }
nullable(const nullable& value) = default;
nullable& operator=(const nullable& value) = default;
nullable& operator=(T value)
{
m_hasValue = true;
m_value = std::move(value);
return *this;
}
nullable& operator=(std::nullptr_t)
{
m_hasValue = false;
m_value = { };
return *this;
}
const T& value() const
{
if (!m_hasValue)
throw bad_nullable_access();
return m_value;
}
T& value()
{
if (!m_hasValue)
throw bad_nullable_access();
return m_value;
}
bool has_value() const { return m_hasValue; }
const T* operator->() const { return &m_value; }
T* operator->() { return &m_value; }
const T& operator*() const { return m_value; }
T& operator*() { return m_value; }
public:
template <typename T2>
friend bool operator==(const nullable<T2>& lhs, const nullable<T2>& rhs);
template <typename T2>
friend bool operator!=(const nullable<T2>& lhs, const nullable<T2>& rhs);
template <typename T2>
friend bool operator==(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs);
template <typename T2>
friend bool operator!=(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs);
template <typename T2>
friend bool operator==(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs);
template <typename T2>
friend bool operator!=(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs);
template <typename T2>
friend bool operator==(const nullable<T2>& lhs, const T2& rhs);
template <typename T2>
friend bool operator==(const T2& lhs, const nullable<T2>& rhs);
template <typename T2>
friend bool operator==(const nullable<T2>& lhs, std::nullptr_t);
template <typename T2>
friend bool operator!=(const nullable<T2>& lhs, const T2& rhs);
template <typename T2>
friend bool operator!=(const T2& lhs, const nullable<T2>& rhs);
template <typename T2>
friend bool operator==(std::nullptr_t, const nullable<T2>& rhs);
template <typename T2>
friend bool operator!=(const nullable<T2>& lhs, std::nullptr_t);
template <typename T2>
friend bool operator!=(std::nullptr_t, const nullable<T2>& rhs);
private:
bool m_hasValue;
T m_value;
};
// Template spacialization for references
template <typename T>
class nullable<T&> final
{
public:
nullable()
: m_hasValue(false), m_value{ } { }
nullable(T& value)
: m_hasValue(true), m_value(&value) { }
nullable(std::nullptr_t)
: m_hasValue(false), m_value{ } { }
nullable(const nullable& value) = default;
nullable& operator=(const nullable& value) = default;
const T& value() const
{
if (!m_hasValue)
throw bad_nullable_access();
return *m_value;
}
T& value()
{
if (!m_hasValue)
throw bad_nullable_access();
return *m_value;
}
bool has_value() const { return m_hasValue; }
const T* operator->() const { return m_value; }
T* operator->() { return m_value; }
const T& operator*() const { return *m_value; }
T& operator*() { return *m_value; }
public:
template <typename T2>
friend bool operator==(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs);
template <typename T2>
friend bool operator!=(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs);
template <typename T2>
friend bool operator==(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs);
template <typename T2>
friend bool operator!=(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs);
template <typename T2>
friend bool operator==(const nullable<T2&>& lhs, const nullable<T2&>& rhs);
template <typename T2>
friend bool operator!=(const nullable<T2&>& lhs, const nullable<T2&>& rhs);
template <typename T2>
friend bool operator==(const nullable<T2&>& lhs, const std::decay_t<T2>& rhs);
template <typename T2>
friend bool operator!=(const nullable<T2&>& lhs, const std::decay_t<T2>& rhs);
template <typename T2>
friend bool operator==(const std::decay_t<T2>& lhs, const nullable<T2&>& rhs);
template <typename T2>
friend bool operator!=(const std::decay_t<T2>& lhs, const nullable<T2&>& rhs);
template <typename T2>
friend bool operator==(const nullable<T2>& lhs, std::nullptr_t);
template <typename T2>
friend bool operator==(std::nullptr_t, const nullable<T2>& rhs);
template <typename T2>
friend bool operator!=(const nullable<T2>& lhs, std::nullptr_t);
template <typename T2>
friend bool operator!=(std::nullptr_t, const nullable<T2>& rhs);
private:
bool m_hasValue;
T* m_value;
};
template <typename T>
using nullableref = nullable<T&>;
template <typename T2>
bool operator==(const nullable<T2>& lhs, const nullable<T2>& rhs)
{
if (lhs.m_hasValue != rhs.m_hasValue)
return false;
if (lhs.m_hasValue)
return lhs.m_value == rhs.m_value;
else
return true;
}
template <typename T2>
bool operator!=(const nullable<T2>& lhs, const nullable<T2>& rhs)
{
if (lhs.m_hasValue != rhs.m_hasValue)
return true;
if (lhs.m_hasValue)
return lhs.m_value != rhs.m_value;
else
return false;
}
template <typename T2>
bool operator==(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs)
{
if (lhs.m_hasValue != rhs.m_hasValue)
return true;
if (lhs.m_hasValue)
return lhs.m_value != *rhs.m_value;
else
return false;
}
template <typename T2>
bool operator!=(const nullable<std::decay_t<T2>>& lhs, const nullable<T2&>& rhs)
{
if (lhs.m_hasValue != rhs.m_hasValue)
return true;
if (lhs.m_hasValue)
return lhs.m_value != *rhs.m_value;
else
return false;
}
template <typename T2>
bool operator==(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs)
{
if (lhs.m_hasValue != rhs.m_hasValue)
return false;
if (lhs.m_hasValue)
return *lhs.m_value == rhs.m_value;
else
return true;
}
template <typename T2>
bool operator!=(const nullable<T2&>& lhs, const nullable<std::decay_t<T2>>& rhs)
{
if (lhs.m_hasValue != rhs.m_hasValue)
return true;
if (lhs.m_hasValue)
return *lhs.m_value != rhs.m_value;
else
return false;
}
template <typename T2>
bool operator==(const nullable<T2&>& lhs, const nullable<T2&>& rhs)
{
if (lhs.m_hasValue != rhs.m_hasValue)
return false;
if (lhs.m_hasValue)
return *lhs.m_value == *rhs.m_value;
else
return true;
}
template <typename T2>
bool operator!=(const nullable<T2&>& lhs, const nullable<T2&>& rhs)
{
if (lhs.m_hasValue != rhs.m_hasValue)
return true;
if (lhs.m_hasValue)
return *lhs.m_value != *rhs.m_value;
else
return false;
}
template <typename T2>
bool operator==(const nullable<T2&>& lhs, const std::decay_t<T2>& rhs)
{
if (!lhs.m_hasValue)
return false;
return *lhs.m_value == rhs;
}
template <typename T2>
bool operator!=(const nullable<T2&>& lhs, const std::decay_t<T2>& rhs)
{
if (!lhs.m_hasValue)
return true;
return *lhs.m_value != rhs;
}
template <typename T2>
bool operator==(const std::decay_t<T2>& lhs, const nullable<T2&>& rhs)
{
if (!rhs.m_hasValue)
return false;
return lhs == *rhs.m_value;
}
template <typename T2>
bool operator!=(const std::decay_t<T2>& lhs, const nullable<T2&>& rhs)
{
if (!rhs.m_hasValue)
return true;
return lhs != *rhs.m_value;
}
template <typename T2>
bool operator==(const nullable<T2>& lhs, const T2& rhs)
{
if (!lhs.m_hasValue)
return false;
return lhs.m_value == rhs;
}
template <typename T2>
bool operator!=(const nullable<T2>& lhs, const T2& rhs)
{
if (!lhs.m_hasValue)
return true;
return lhs.m_value != rhs;
}
template <typename T2>
bool operator==(const T2& lhs, const nullable<T2>& rhs)
{
if (!rhs.m_hasValue)
return false;
return lhs == rhs.m_value;
}
template <typename T2>
bool operator!=(const T2& lhs, const nullable<T2>& rhs)
{
if (!rhs.m_hasValue)
return true;
return lhs != rhs.m_value;
}
template <typename T2>
bool operator==(const nullable<T2>& lhs, std::nullptr_t)
{
return !lhs.m_hasValue;
}
template <typename T2>
bool operator!=(const nullable<T2>& lhs, std::nullptr_t)
{
return lhs.m_hasValue;
}
template <typename T2>
bool operator==(std::nullptr_t, const nullable<T2>& rhs)
{
return !rhs.m_hasValue;
}
template <typename T2>
bool operator!=(std::nullptr_t, const nullable<T2>& rhs)
{
return rhs.m_hasValue;
}
}
#endif // COMMON_NULLABLE_H