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.
Related
In this code, why is it not possible to access the private field of my class in the operator overload ?
(Note that this is only a MRE, not the full code)
template <typename T>
class Frac
template <typename T, typename U>
Frac<T> operator+ (Frac<T> lhs,const Frac<U>& rhs);
template <typename T, typename U>
bool operator==(const Frac<T>& lhs, const Frac<U>& rhs);
template <typename T>
class Frac {
template<typename>
friend class Frac;
friend Frac operator+ <>(Frac lhs,const Frac& rhs);
friend bool operator== <>(const Frac& lhs, const Frac& rhs);
private:
T numerator, denominator;
};
template <typename T, typename U>
Frac<T> operator+(Frac<T> lhs,const Frac<U>& rhs) {
lhs.denominator += rhs.denominator;
return lhs;
}
template <typename T, typename U>
bool operator==(const Frac<T>& lhs, const Frac<U>& rhs) {
return (lhs.numerator == rhs.numerator && lhs.denominator == rhs.denominator);
}
When I compile the compiler tells me that it is not possible to access the denominator and numerator fields because they are private. However the overload is indicated as friendly. The class is also indicated as friendly so that all instances of the class whatever the type are friendly.
Could someone explain me what the problem is and how to solve it?
To make each instance of
template <typename T, typename U>
bool operator==(const Frac<T>& lhs, const Frac<U>& rhs);
a friend, you need to be just as verbose in your friend declaration. Copy this declaration and stick "friend" in it. There are two quirks. First, template has to come before friend, so you'll be adding the keyword in the middle of the declaration. Second, T is already being used as the template parameter to the class, so you should choose a different identifier to avoid shadowing (I'll use S).
template <typename S, typename U>
// ^^^
friend bool operator==(const Frac<S>& lhs, const Frac<U>& rhs);
// ^^^^^^ ^^^
Without this change, you are saying that the friend of Frac<T> is an operator that takes two Frac<T> parameters (the same T).
it is not possible to access the denominator and numerator fields because they are private.
Yes, you haven't made the free functions friends. You've made the classes friends, but that doesn't help the free functions. One simpler solution is to define them in the class definition.
Example:
template <typename U>
friend Frac operator+(Frac lhs, const Frac<U>& rhs) {
lhs.denominator += rhs.denominator;
return lhs;
}
However, operator+ could be implemented as a free function without any friendship if you instead make operator+= a member function. The friendship between all Frac<>s has already been established so no additional friend declarations are needed.
Example:
#include <iostream>
#include <utility>
template <typename T>
class Frac {
public:
template <typename> // be friends with all Frac's
friend class Frac;
Frac() = default; // needed because for the templated converting ctor below
// a converting constructor from any Frac:
template<class U>
explicit Frac(const Frac<U>& rhs) :
numerator(rhs.numerator), denominator(rhs.denominator) {}
template <typename U>
Frac& operator+=(const Frac<U>& rhs) {
denominator += rhs.denominator; // ok: rhs has befriended Frac<T>
return *this;
}
template <typename U>
bool operator==(const Frac<U>& rhs) const {
// ok: rhs has befriended Frac<T> here too
return numerator == rhs.numerator && denominator == rhs.denominator;
}
private:
T numerator{}, denominator{};
};
// This free function doesn't need to be a friend. It uses the member function
// operator+=
// The returned type, Fact<R>, is deduced by fetching the type you'd gotten
// if you add a T and U.
template<typename T, typename U,
typename R = decltype(std::declval<T>() + std::declval<U>())>
Frac<R> operator+(const Frac<T>& lhs, const Frac<U>& rhs) {
Frac<R> rv(lhs); // use the converting constructor
rv += rhs;
return rv;
}
int main() {
Frac<int> foo;
Frac<double> bar;
auto r = foo + bar; // r is a Frac<double> (int + double => double)
}
Suppose I have a MyCustomType that has comparisons with SomeOtherType:
struct SomeOtherType {
int value;
constexpr bool operator==(const SomeOtherType& rhs) const = default;
};
struct MyCustomType {
int x;
constexpr bool operator==(const MyCustomType& rhs) const = default;
constexpr bool operator==(const SomeOtherType& rhs) const {
return x == rhs.value;
}
friend constexpr bool operator==(const SomeOtherType& lhs, const MyCustomType& rhs) {
return lhs.value == rhs.x;
}
};
This is great, but static_assert(std::equality_comparable_with<MyCustomType, SomeOtherType>); fails, meaning that I can't use them for heterogenous lookup in the std::ranges algorithms:
error: no type named 'type' in 'struct std::common_reference<const MyCustomType&, const SomeOtherType&>'
I understand why this fails: we may have an operator==, but we don't meet the common-reference requirements (see also Does `equality_comparable_with` need to require `common_reference`? ). However, my type is, practically speaking, equality comparable with SomeOtherType. How can I convince the compiler that this is the case?
Philosophically, the common-reference requirements of std::equality_comparable_with are explicitly encoding the implicit statement made when writing a heterogenous operator==(T, U) to actually mean equality*: there is some common supertype "T union U" for which the operator== is equality. This "T union U" doesn't actually exist in the code for MyCustomType and SomeOtherType. If we make the type actually exist by specializing std::common_reference_t, then we can meet std::equality_comparable_with.
*Some types use operator== for equivalence rather than equality (e.g. iterators+sentinels), and as such should not and do not meet std::equality_comparable_with.
We can use the std::basic_common_reference customization point to specify a proxy reference:
The class template basic_common_reference is a customization point that allows users to influence the result of common_reference for user-defined types (typically proxy references).
For this to work, we need:
eq_proxy_ref<T> which acts like a reference for T.
MyCustomType must be implicitly convertible to eq_proxy_ref<T>.
SomeOtherType must be implicitly convertible to eq_proxy_ref<T>.
basic_common_reference of MyCustomType and SomeOtherType must return this eq_proxy_ref<int>. A eq_proxy_ref<MyCustomProxy> could work too if you wanted to avoid leaking the internals of MyCustomType.
eq_proxy_ref<T> must have comparison operators between itself.
eq_proxy_ref<T> must obey the spirit of the requirement.
Beware, however, that std::common_reference_t is used for more than just equality, including std::three_way_comparable_with, std::totally_ordered_with, and some of the Ranges algorithms or views. As such, your eq_proxy_ref<T> should actually be a common reference for your two types, not just a mechanism to enable equality.
An example meeting these constraints follows:
#include <concepts>
#include <type_traits>
// Assuming you don't own SomeOtherType:
template <typename T>
class MyCustomTypeEqProxy {
template <typename>
friend class MyCustomTypeEqProxy;
private:
T ref_;
public:
template <typename U>
requires std::convertible_to<U, T>
constexpr MyCustomTypeEqProxy(U ref)
: ref_(ref)
{}
constexpr MyCustomTypeEqProxy(const SomeOtherType& rhs)
requires std::convertible_to<const int&, T>
: ref_(rhs.value)
{}
template <typename U>
requires std::equality_comparable_with<T, U>
constexpr bool operator==(const MyCustomTypeEqProxy<U>& rhs) const {
return ref_ == rhs.ref_;
};
};
struct MyCustomType {
int x;
constexpr bool operator==(const MyCustomType& rhs) const = default;
constexpr bool operator==(const SomeOtherType& rhs) const {
return x == rhs.value;
}
friend constexpr bool operator==(const SomeOtherType& lhs, const MyCustomType& rhs) {
return lhs.value == rhs.x;
}
constexpr operator MyCustomTypeEqProxy<int>() const { return MyCustomTypeEqProxy<int>(x); }
};
namespace std {
// May not be needed, but allows the custom proxy reference to expand to common references
// of what we're comparing against.
template <typename T, typename U, template <typename> class TQ, template <typename> class UQ>
struct basic_common_reference<::MyCustomTypeEqProxy<T>, U, TQ, UQ> {
using type = ::MyCustomTypeEqProxy< std::common_reference_t<T, UQ<U>> >;
};
template <typename T, typename U, template <typename> class TQ, template <typename> class UQ>
struct basic_common_reference<T, ::MyCustomTypeEqProxy<U>, TQ, UQ> {
using type = ::MyCustomTypeEqProxy< std::common_reference_t<TQ<T>, U> >;
};
// Tell std::common_reference_t about MyCustomTypeEqProxy
template <template <typename> class LQ, template <typename> class RQ>
struct basic_common_reference<::MyCustomType, ::SomeOtherType, LQ, RQ> {
using type = ::MyCustomTypeEqProxy<int>;
};
template <template <typename> class LQ, template <typename> class RQ>
struct basic_common_reference<::SomeOtherType, ::MyCustomType, LQ, RQ> {
using type = ::MyCustomTypeEqProxy<int>;
};
}
Compiler Explorer link
I suspect that I missed some nuances, but this is enough to meet std::equality_comparable_with.
EDIT: Prolog: I'm a victim of my own ignorance and also of late-night coding.
I'm writing a templated class using template template. It has an iterator, which means that I need to provide an appropriately templated operator==(). This is where I'm having trouble.
Representative code sample follows:
#include <iostream>
#include <typeinfo>
using namespace std;
namespace detail {
template <typename T> class foo {};
template <typename T> class bar {};
}
template <template<class> class A, template<class> class B>
struct basic_thing {
template <typename T> using target_type = A<B<T>>;
target_type<float> fmember;
target_type<int> imember;
struct iterator {
bool equal (const iterator& other) { return true; }
};
iterator begin () { return iterator{}; }
iterator end () { return iterator{}; }
};
template <template<class> class A, template<class> class B>
bool operator== (const typename basic_thing<A, B>::iterator& lhs, const typename basic_thing<A, B>::iterator& rhs) {
return lhs.equal(rhs);
}
int main ()
{
using Thing = basic_thing<detail::foo, detail::bar>;
Thing t;
cout << typeid(t.fmember).name() << endl;
cout << typeid(t.imember).name() << endl;
bool b = (t.begin() == t.end());
return 0;
}
My goal here is to provide a composable way to define basic_thing::target_type, and this pattern works for that purpose. But, I'm stuck at how to declare operator==() for basic_thing::iterator. Either this isn't very straightforward, or there's something obvious that I'm missing. (Likely the latter.)
g++-7.4.0 with -std=c++11 produces the following:
foo.cc: In function 'int main()':
foo.cc:39:23: error: no match for 'operator==' (operand types are 'basic_thing<detail::foo, detail::bar>::iterator' and 'basic_thing<detail::foo, detail::bar>::iterator')
bool b = (t.begin() == t.end());
~~~~~~~~~~^~~~~~~~~~
foo.cc:27:6: note: candidate: template<template<class> class A, template<class> class B> bool operator==(const typename basic_thing<A, B>::iterator&, const typename basic_thing<A, B>::iterator&)
bool operator== (const typename basic_thing<A, B>::iterator& lhs, const typename basic_thing<A, B>::iterator& rhs) {
^~~~~~~~
foo.cc:27:6: note: template argument deduction/substitution failed:
foo.cc:39:32: note: couldn't deduce template parameter 'template<class> class A'
bool b = (t.begin() == t.end());
^
What are some correct ways to do this? Is it even possible when template templates are involved?
The simpler way is to create it inside the struct directly (as member or friend function):
template <template<class> class A, template<class> class B>
struct basic_thing {
// ...
struct iterator {
bool equal (const iterator& other) { return true; }
bool operator ==(const iterator& rhs) const;
// friend bool operator ==(const iterator& lhs, const iterator& rhs);
};
};
With
template <template<class> class A, template<class> class B>
bool operator== (const typename basic_thing<A, B>::iterator& lhs,
const typename basic_thing<A, B>::iterator& rhs);
A and B are not deducible (on the left of ::).
so only callable the ugly way:
bool b = operator==<detail::foo, detail::bar>(t.begin(), t.begin());
I am trying to implement an overload for operator!= that compares two objects of different types (InnerA and InnerB). Both of the types are defined as nested classes within a template class (Outer). The overload needs to be a friend of both classes as it accesses private fields from each.
template<typename Type> class Outer
{
public:
class InnerA;
class InnerB;
};
template<typename Type> bool operator!=(const typename Outer<Type>::InnerA& lhs, const typename Outer<Type>::InnerB& rhs);
template<typename Type> class Outer<Type>::InnerA
{
const int val = 0;
friend bool operator!=<>(const InnerA& lhs, const typename Outer<Type>::InnerB& rhs);
};
template<typename Type> class Outer<Type>::InnerB
{
const int val = 1;
friend bool operator!=<>(const typename Outer<Type>::InnerA& lhs, const InnerB& rhs);
};
template<typename Type> bool operator!=(const typename Outer<Type>::InnerA& lhs, const typename Outer<Type>::InnerB& rhs)
{
return lhs.val != rhs.val;
}
int main()
{
bool b = Outer<int>::InnerA() != Outer<int>::InnerB();
}
The above code fails to compile, emitting:
In instantiation of 'class Outer<int>::InnerA':
34:33: required from here
15:17: error: template-id 'operator!=<>' for 'bool operator!=(const Outer<int>::InnerA&, const Outer<int>::InnerB&)' does not match any template declaration
In instantiation of 'class Outer<int>::InnerB':
34:57: required from here
22:17: error: template-id 'operator!=<>' for 'bool operator!=(const Outer<int>::InnerA&, const Outer<int>::InnerB&)' does not match any template declaration
In function 'int main()':
34:35: error: no match for 'operator!=' (operand types are 'Outer<int>::InnerA' and 'Outer<int>::InnerB')
34:35: note: candidate is:
26:30: note: template<class Type> bool operator!=(const typename Outer<Type>::InnerA&, const typename Outer<Type>::InnerB&)
26:30: note: template argument deduction/substitution failed:
34:57: note: couldn't deduce template parameter 'Type'
While there might be better ways to achieve a similar result, I'm curious as to what precisely is wrong with my code. Thanks!
template<typename Type> class Outer<Type>::InnerA
{
friend bool operator!=(const InnerA& lhs, const typename Outer<Type>::InnerB& rhs) { return true; }
};
template<typename Type> class Outer<Type>::InnerB
{
friend bool operator!=(const typename Outer<Type>::InnerA& lhs, const InnerB& rhs) { return true; }
};
these are non-template friends. They also conflict. So implement one of them, omit the other.
They will be found via ADL.
I found the following workaround, refactoring the overload as a method of one of the nested classes:
template<typename Type> class Outer
{
public:
class InnerA;
class InnerB;
};
template<typename Type> class Outer<Type>::InnerA
{
const int val = 0;
public:
bool operator!=(const typename Outer<Type>::InnerB& other);
};
template<typename Type> class Outer<Type>::InnerB
{
const int val = 1;
friend bool Outer<Type>::InnerA::operator!=(const InnerB& other);
};
template<typename Type> bool Outer<Type>::InnerA::operator!=(const typename Outer<Type>::InnerB& other)
{
return val != other.val;
}
int main()
{
bool b = Outer<void>::InnerA() != Outer<void>::InnerB();
}
However, I am still curious if the same can be accomplished using a non-member friend function as in the question.
The issue with the code in the question ended up being that template deduction is not attempted on type names nested inside a dependent type (e.g. Outer<Type>::Inner).
This question is essentially a duplicate of Nested template and parameter deducing. A detailed explanation on why this is a problem can be found here.
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.