How to prevent implicit instantiation of a template function - c++

I have a class with several strongly typed enums and I want them to be implicitly comparable to int. For that purpose I have written super-simple template function:
template<class T> bool operator==(const T &e, const int &i)
{
return (static_cast<int>(e) == i);
}
and the opposite one
template<class T> bool operator==(const int &i, const T &e)
{
return (e == i);
}
However this might be dangerous because it pretty much writes a blank check for any comparison between class T and an integer. I would therefore want only few explicitly specified instantiations such as
template bool operator==(const MyEnum &e, const int &i);
template bool operator==(const int &i, const MyEnum &e);
and no other. Is there a way to disable all implicit instantiations or achieve this? I can always write both operators for each enum separately of course but templates really seem like a better solution.

Use SFINAE to reject overloads which don't deduce a MyEnum:
template<class T, std::enable_if_t<std::is_same<T, MyEnum>::value>* = nullptr>
bool operator==(const T &e, const int &i);
template<class T, std::enable_if_t<std::is_same<T, MyEnum>::value>* = nullptr>
bool operator==(const int &i, const T &e);
Due to the comments I have updated my answer. If you want to only accept select few template types then I suggest using this bool_or trick:
#include <type_traits>
#include <utility>
template<bool... Bs>
using bool_sequence = std::integer_sequence<bool, Bs...>;
template<bool... Bs>
using bool_and = std::is_same<bool_sequence<Bs...>,
bool_sequence<(Bs || true)...>>;
template<bool... Bs>
using bool_or = std::integral_constant<bool, !bool_and<!Bs...>::value>;
template<class T, class... Ts>
using any_same = bool_or<std::is_same<T, Ts>::value...>;
template<class T, std::enable_if_t<any_same<T, MyEnum, X, Y, Z>::value>* = nullptr>
bool operator==(const T &e, const int &i);
template<class T, std::enable_if_t<any_same<T, MyEnum, X, Y, Z>::value>* = nullptr>
bool operator==(const int &i, const T &e);
You can add any number of types to compare with.

Related

Template constraints on operator overloading does not work as expected

#include <tuple>
#include <utility>
template<typename T>
struct is_tuple_like : std::false_type {};
template<typename... Ts>
struct is_tuple_like<std::tuple<Ts...>> : std::true_type {};
template<typename T, typename U>
struct is_tuple_like<std::pair<T, U>> : std::true_type {};
template<typename T>
concept tuple_like = is_tuple_like<T>::value;
template<tuple_like L, tuple_like R, int N = std::tuple_size_v<L>>
auto operator*(const L &lhs, const R &rhs) { return 0; }
enum { Enum };
int main()
{
Enum * Enum; // causes compilation error
return 0;
}
You can run the code here: http://coliru.stacked-crooked.com/a/f65e333060f40e60
I have defined a concept so-called tuple_like and overloaded operator*() using the concept.
Then, If I multiply enums, my overloaded operator*() for tuple_like is picked up and the compiler complains missing std::tuple_size for enum.
What did I do wrong here and how can I fix it without overload for each class templates - std::tuple and std::pair?
FYI, even if it's unusual, I cannot remove the part of multiplying enums because it's not my code.
Turn tuple_size_v into tuple_size::value to enable SFINAE
template<tuple_like L, tuple_like R, int N = std::tuple_size<L>::value>
auto operator*(const L &lhs, const R &rhs) { return 0; }
However, I don't see any value in declaring an extra template parameter N here. The type that satisfies tuple_like already guarantees that tuple_size_v is a valid value.
It would be more appropriate to move N into the function body
template<tuple_like L, tuple_like R>
auto operator*(const L &lhs, const R &rhs) {
constexpr auto N = std::tuple_size_v<L>; // guaranteed to work
// uses N below
}
Looks like gcc can't handle SFINAE for default values of template arguments. This workaround fixes it:
template<tuple_like L, tuple_like R, int N>
auto operator*(const L &lhs, const R &rhs) { return 0; }
template<tuple_like L, tuple_like R>
auto operator*(const L &lhs, const R &rhs) { return operator*<L, R, std::tuple_size_v<L>>(lhs, rhs); }

Implicit type conversion for operator==

I'd like to have a way to compare different data types that are internally represented by an array (e.g. a string and a vector of chars) using a common array reference type. Consider the following code:
template <typename T>
struct ArrayConstRef {
const T *data;
size_t length;
};
template <typename T>
bool operator==(ArrayConstRef<T> a, ArrayConstRef<T> b);
template <typename T>
class ContainerA {
public:
operator ArrayConstRef<T>() const;
explicit operator const T *() const;
};
template <typename T>
class ContainerB {
public:
operator ArrayConstRef<T>() const;
explicit operator const T *() const;
};
int main() {
if (ContainerA<int>() == ContainerB<int>()) // error - no matching operator==
printf("equals\n");
return 0;
}
The overloaded operator== isn't matched even though the implicit conversion is available. Interestingly, if I removed the explicit keywords, the compiler manages to convert both objects to pointers and do the comparison that way (which I don't want). Why does one implicit conversion work but not the other? Is there a way to make it work?
This can be solved using SFINAE and little changes in code of your classes.
#include <cstddef>
#include <cstdio>
#include <type_traits>
template <typename T>
struct ArrayConstRef {
const T *data;
size_t length;
};
// This is needed to override other template below
// using argument depended lookup
template <typename T>
bool operator==(ArrayConstRef<T> a, ArrayConstRef<T> b){
/* Provide your implementation */
return true;
}
template <
typename Left,
typename Right,
// Sfinae trick :^)
typename = std::enable_if_t<
std::is_constructible_v<ArrayConstRef<typename Left::ItemType>, const Left&>
&& std::is_constructible_v<ArrayConstRef<typename Right::ItemType>, const Right&>
&& std::is_same_v<typename Left::ItemType, typename Right::ItemType>
>
>
inline bool operator==(const Left& a, const Right& b){
using T = typename Left::ItemType;
return ArrayConstRef<T>(a) == ArrayConstRef<T>(b);
}
template <typename T>
class ContainerA {
public:
// Add type of element
using ItemType = T;
operator ArrayConstRef<T>() const;
explicit operator const T *() const;
};
template <typename T>
class ContainerB {
public:
// Add type of element
using ItemType = T;
operator ArrayConstRef<T>() const;
explicit operator const T *() const;
};
int main() {
if (ContainerA<int>() == ContainerB<int>()) // no error :)
printf("equals\n");
return 0;
}
Compiles well with GCC 11.2 -std=c++17.
If you can use C++20, it is better to use concepts for this.
Code below compiles with GCC 11.2 -std=c++20.
#include <cstddef>
#include <cstdio>
#include <type_traits>
template <typename T>
struct ArrayConstRef {
const T *data;
size_t length;
};
// This is needed to override other template below
// using argument depended lookup
template <typename T>
bool operator==(ArrayConstRef<T> a, ArrayConstRef<T> b){
/* Provide your implementation */
return true;
}
template <typename Container>
concept ConvertibleToArrayConstRef =
requires (const Container& a) {
ArrayConstRef<typename Container::ItemType>(a);
};
template <
ConvertibleToArrayConstRef Left,
ConvertibleToArrayConstRef Right
>
requires (std::is_same_v<
typename Left::ItemType,
typename Right::ItemType>
)
inline bool operator==(const Left& a, const Right& b){
using T = typename Left::ItemType;
return ArrayConstRef<T>(a) == ArrayConstRef<T>(b);
}
template <typename T>
class ContainerA {
public:
// Add type of element
using ItemType = T;
operator ArrayConstRef<T>() const;
explicit operator const T *() const;
};
template <typename T>
class ContainerB {
public:
// Add type of element
using ItemType = T;
operator ArrayConstRef<T>() const;
explicit operator const T *() const;
};
int main() {
if (ContainerA<int>() == ContainerB<int>()) // no error :)
printf("equals\n");
return 0;
}
The problem is that operator== is a template, for it to be called the template parameter T needs to be deduced. But implicit conversion (ContainerA<int> -> ArrayConstRef<int> and ContainerB<int> -> ArrayConstRef<int>) won't be considered in template argument deduction.
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
On the other hand, if you specify the template argument to bypass the deduction (in a ugly style), the code works.
if (operator==<int>(ContainerA<int>(), ContainerB<int>()))
Another workaround, you might change the parameter type and add a helper like:
template <typename T>
bool equal_impl(ArrayConstRef<T> a, ArrayConstRef<T> b);
template <template <typename> class C1, template <typename> class C2, typename T>
std::enable_if_t<std::is_convertible_v<C1<T>, ArrayConstRef<T>>
&& std::is_convertible_v<C2<T>, ArrayConstRef<T>>,
bool>
operator==(C1<T> a, C2<T> b) {
return equal_impl<T>(a, b);
}
LIVE

Getting sane comparators with boost::variant

I have the following boost::variant type
using MyType = boost::variant<int, double, char, std::string, bool>;
and I would like to be able to use this type in as natural way as possible, in particular I'd like to be able to use the comparison operators such that comparison of types that would compile, give then correct result, otherwise behaviour is undefined (or an exception is thrown). For example suppose I have
MyType x {2}; // int
MyType y {1.0}; // double
MyType z {"hello"}; // std::string
I'd like to be able to compare x with y in the same way as comparing int {2} and double {1.0}. boost::variant already defined operator< etc, so I can make this comparison, but the result is not as expected.
cout << (x < y) << endl; // 1
cout << (y < x) << endl; // 0
I can define the desired behaviour using a boost::static_visitor
template <typename T, typename U, typename R = MyType>
using enable_if_both_arithmetic = std::enable_if_t<std::is_arithmetic<T>::value && std::is_arithmetic<U>::value, R>;
template <typename T, typename U, typename R = MyType>
using enable_if_not_both_arithmetic = std::enable_if_t<!(std::is_arithmetic<T>::value && std::is_arithmetic<U>::value), R>;
class is_less_than : public boost::static_visitor<bool>
{
public:
template <typename T>
bool operator()(const T& lhs, const T& rhs) const
{
return lhs < rhs;
}
template <typename T, typename U>
enable_if_both_arithmetic<T, U, bool> operator()(const T& lhs, const U& rhs) const
{
return lhs < rhs;
}
template <typename T, typename U>
enable_if_not_both_arithmetic<T, U, bool> operator()(const T& lhs, const U& rhs) const
{
return false; // or could throw
}
};
Which can be used like
cout << boost::apply_visitor(is_less_than(), x, y) << endl; // 0
but this is long and ugly. Is there someway I can 'overwrite' the boost::variant::operator< and use my own?
bool operator<(const VcfType& lhs, const VcfType& rhs)
{
return boost::apply_visitor(is_less_than(), lhs, rhs);
}
You are allowed to define a subclass containing the operator< you need, either of the boost::variant<int, double, char, std::string, bool> template instantiation (probably simpler) or of the boost::variant<> template (if you have other cases to cover).

Using std::enable_if

I'm trying to specialize a function for a range of types using std::enable_if.
Here is a a simpler version of what I'm trying to accomplish.
#include <type_traits>
#include <string>
struct Ip
{};
template <typename T>
bool Equal(const std::string& s, const T& data)
{return s == data;}
template <typename T>
bool Equal(const std::string& s, const typename std::enable_if<std::is_integral<T>::value, T>::type& data)
{
//int specific
}
template <typename T>
bool Equal(const std::string& s, const typename std::enable_if<std::is_floating_point<T>::value, T>::type& data)
{
//Float specific
}
//Specialization
template <> bool Equal(const std::string& s, const Ip& data)
{
//Ip specific
}
int main()
{
//Equal("21",21); // Do not compile
Equal("21","42.5"); // Compile
}
but when trying to compile, the template functions with std::enable_if does not seem to participate in the resolution, and so the compiler tells me that there is no function that match my function call. I tried using std::enable_if with the return type, but no luck there either. I'm sure there is something I'm doing terribly wrong in this code, or maybe I'm trying to do this the wrong way. I'm trying to not write every int specialization (int, short, long, long long, ...), so does anyone have a solution for this ?
I'd make several changes to your code, beginning with the unconstrained function template. Since you're handling arithmetic types separately, that should only be selected if T is neither integral nor floating point.
template <typename T>
typename std::enable_if<!std::is_integral<T>::value &&
!std::is_floating_point<T>::value, bool>::type
Equal(const std::string& s, const T& data)
{return s == data;}
Now, the way you've defined Equal for integral and floating point types causes the T to be a non-deduced context. So move the enable_if to a dummy template parameter to allow deduction (you could also use the return type, as above).
template <typename T,
typename std::enable_if<std::is_integral<T>::value, T>::type* = nullptr>
bool Equal(const std::string& s, const T& data)
{
//int specific
return false;
}
template <typename T,
typename std::enable_if<std::is_floating_point<T>::value, T>::type* = nullptr>
bool Equal(const std::string& s, const T& data)
{
//Float specific
return false;
}
Finally, there's no need to specialize for handling Ip, create an overload instead.
//Overload
bool Equal(const std::string& s, const Ip& data)
{
//Ip specific
return false;
}
Live demo
Following works: (https://ideone.com/G1vJKm)
#include <string>
#include <type_traits>
struct Ip
{
std::string ip;
};
template <typename T>
typename std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value, bool>::type
Equal(const std::string& s, const T& data)
{return s == data;}
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
Equal(const std::string& s, const T& data)
{
//int specific
return false;
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
Equal(const std::string& s, const T& data)
{
//Float specific
return false;
}
bool Equal(const std::string& s, const Ip& data)
{
//Ip specific
return false;
}
In your case,
template <typename T>
bool Equal(const std::string& s, const T& data)
is a valid candidate for all T, also for integral and floating point.
Your other template functions, compiler can't deduce type T for typename std::enable_if<std::is_integral<T>::value, T>::type.
And for the Ip version a simpler overload is sufficient (and preferred).

Do I have to implement commutativity for comparison operators manually?

With n different classes, which should all be comparable with operator== and operator!=, it would be necessary to implement (n ^ 2 - n) * 2 operators manually. (At least I think that's the term)
That would be 12 for three classes, 24 for four. I know that I can implement a lot of them in terms of other operators like so:
operator==(A,B); //implemented elsewhere
operator==(B,A){ return A == B; }
operator!=(A,B){ return !(A == B); }
but it still seems very tedious, especially because A == B will always yield the same result as B == A and there seems to be no reason whatsoever to implement two version of them.
Is there a way around this? Do I really have to implement A == B and B == A manually?
Use Boost.Operators, then you only need to implement one, and boost will define the rest of the boilerplate for you.
struct A
{};
struct B : private boost::equality_comparable<B, A>
{
};
bool operator==(B const&, A const&) {return true;}
This allows instances of A and B to be compared for equality/inequality in any order.
Live demo
Note: private inheritance works here because of the Barton–Nackman trick.
In the comments the problem is further explained by stating that all of the types are really different forms of smart pointers with some underlying type. Now this simplifies quite a lot the problem.
You can implement a generic template for the operation:
template <typename T, typename U>
bool operator==(T const & lhs, U const & rhs) {
return std::addressof(*lhs) == std::addressof(*rhs);
}
Now this is a bad catch all (or rather catch too many) implementation. But you can narrow down the scope of the operator by providing a trait is_smart_ptr that detects whether Ptr1 and Ptr2 are one of your smart pointers, and then use SFINAE to filter out:
template <typename T, typename U,
typename _ = typename std::enable_if<is_pointer_type<T>::value
&& is_pointer_type<U>::value>::type >
bool operator==(T const & lhs, U const & rhs) {
return std::addressof(*lhs) == std::addressof(*rhs);
}
The type trait itself can be just a list of specializations of a template:
template <typename T>
struct is_pointer_type : std::false_type {};
template <typename T>
struct is_pointer_type<T*> : std::true_type {};
template <typename T>
struct is_pointer_type<MySmartPointer<T>> : std::true_type {};
template <typename T>
struct is_pointer_type<AnotherPointer<T>> : std::true_type {};
It probably makes sense not to list all of the types that match the concept of pointer, but rather test for the concept, like:
template <typename T, typename U,
typename _ = decltype(*declval<T>())>
bool operator==(T const & lhs, U const & rhs) {
return std::addressof(*lhs) == std::addressof(*rhs);
}
Where the concept being tested is that it has operator* exists. You could extend the SFINAE check to verify that the stored pointer types are comparable (i.e. that std::addressof(*lhs) and std::addressof(*rhs) has a valid equality:
template <typename T, typename U,
typename _ = decltype(*declval<T>())>
auto operator==(T const & lhs, U const & rhs)
-> decltype(std::addressof(*lhs) == std::addressof(*rhs))
{
return std::addressof(*lhs) == std::addressof(*rhs);
}
And this is probably as far as you can really get: You can compare anything that looks like a pointer to two possibly unrelated objects, if raw pointers to those types are comparable. You might need to single out the case where both arguments are raw pointers to avoid this entering out into a recursive requirement...
not necesarily:
template<class A, class B>
bool operator==(const A& a, const B& b)
{ return b==a; }
works for whatever A and B there is a B==A implementation (otherwise will recourse infinitely)
You can also use CRTP if you don't want the templetized == to work for everything:
template<class Derived>
class comparable {};
class A: public comparable<A>
{ ... };
class B: public comparable<B>
{ ... };
bool operator==(const A& a, const B& b)
{ /* direct */ }
// this define all reverses
template<class T, class U>
bool operator==(const comparable<T>& sa, const comparable<U>& sb)
{ return static_cast<const U&>(sb) == static_cast<const T&>(sa); }
//this defines inequality
template<class T, class U>
bool operator!=(const comparable<T>& sa, const comparable<U>& sb)
{ return !(static_cast<const T&>(sa) == static_cast<const U&>(sb)); }
Using return type SFINAE yo ucan even do something like
template<class A, class B>
auto operator==(const A& a, const B& b) -> decltype(b==a)
{ return b==a; }
template<class A, class B>
auto operator!=(const A& a, const B& b) -> decltype(!(a==b))
{ return !(a==b); }
The goal here is to deal with large n reasonably efficiently.
We create an order, and forward all comparison operators to comp after reordering them to obey that order.
To do this, I start with some metaprogramming boilerplate:
template<class...>struct types{using type=types;};
template<class T,class types>struct index_of{};
template<class T,class...Ts>struct index_of<T,types<T,Ts...>>:
std::integral_constant<unsigned,0>
{};
template<class T,class U,class...Us>struct index_of<T,types<U,Us...>>:
std::integral_constant<unsigned,1+index_of<T,types<Us...>>::value>
{};
which lets us talk about ordered lists of types. Next we use this to impose an order on these types:
template<class T, class U,class types>
struct before:
std::integral_constant<bool, (index_of<T,types>::value<index_of<U,types>::value)>
{};
Now we make some toy types and a list:
struct A{}; struct B{}; struct C{};
typedef types<A,B,C> supp;
int comp(A,B);
int comp(A,C);
int comp(B,C);
int comp(A,A);
int comp(B,B);
int comp(C,C);
template<class T,class U>
std::enable_if_t<before<T,U,supp>::value, bool>
operator==(T const& t, U const& u) {
return comp(t,u)==0;
}
template<class T,class U>
std::enable_if_t<!before<T,U, supp>::value, bool>
operator==(T const& t, U const& u) {
return comp(u,t)==0;
}
template<class T,class U>
std::enable_if_t<before<T,U,supp>::value, bool>
operator<(T const& t, U const& u) {
return comp(t,u)<0;
}
template<class T,class U>
std::enable_if_t<!before<T,U, supp>::value, bool>
operator<(T const& t, U const& u) {
return comp(u,t)>0;
}
etc.
The basic idea is that supp lists the types you want to support, and their prefered order.
Boilerplate operators then forward everything to comp.
You need to implement n*(n-1)/2 comps to handles each pair, but only in one order.
Now for the bad news: probably you want to lift each type to some common type and compare there, rather than het lost in the combinatorial morass.
Suppose you can define a type Q which can store the imformation required to sort any of them.
Then write convert-to-Q code from each type, and implement comparison on Q. This reduces the code written to O(a+b), where a is the number of types and b the number of operators supported.
As an example, smart pointers can be pointer-ordered between each other this way.