How to avoid this sentence is false in a template SFINAE?

折月煮酒 提交于 2019-11-30 01:35:04

The problem with your approach seems to be that the fallback global definition of operator != is too attractive, and you need a SFINAE check to rule it out. However, the SFINAE check depends on the eligibility of the function itself for overload resolution, thus leading to an (attempted) infinite recursion during type deduction.

It seems to me that any similar attempt based on SFINAE would crash against the same wall, so the most sane approach is in my opinion to make your operator != a bit less appealing for overload resolution in the first place, and let other, reasonably written (this will be clear in a moment) overloads of operator != take precedence.

Given the type trait can_equal you provided:

#include <type_traits>
#include <functional>

template<typename T, typename U, typename=void>
struct can_equal : std::false_type {};

template<typename T, typename U>
struct can_equal<
   T,
   U,
   typename std::enable_if<
      std::is_convertible<
         decltype( std::declval<T>() == std::declval<U>() ),
         bool
      >::value
   >::type
>: std::true_type {};

I would define the fallback operator != this way:

template<typename T, typename U>
bool is_not_equal(T&& t, U&& u)
{
    return !(std::forward<T>(t) == std::forward<U>(u));
}

template<
    typename T,
    typename... Ts,
    typename std::enable_if<can_equal<T, Ts...>::value>::type* = nullptr
    >
bool operator != (T const& t, Ts const&... args)
{
    return is_not_equal(t, args...);
}

As far as I know, any overload of operator != that will define exactly two function parameters (so no argument pack) will be a better fit for overload resolution. Therefore, the above, fallback version of operator != will be picked only when no better overload exist. Moreover, it will be picked only if the can_equal<> type trait will return true.

I've tested this against the SSCCE you prepared, where four structs are defined together with some overloads of operator == and operator !=:

struct test { };

bool operator==(const test&, const test&) { std::cout << "(==)"; return true; }
bool operator!=(const test&, const test&) { std::cout << "(!==)"; return true; }

struct test2 { };

struct test3 { };
bool operator == (const test3&, const test3&) 
{ std::cout << "(==)"; return true; }

struct test4 { };

template<typename T, 
         EnableIf< std::is_convertible< T, test4 const& >::value >... >
bool operator == ( T&&, T&& ) { std::cout << "(==)"; return true; }

template<typename T, 
         EnableIf< std::is_convertible< T, test4 const& >::value >... >
bool operator != ( T&&, T&& ) { std::cout << "(!=)"; return true; }

To verify that the desired output is produced and mirror what you did in your original version of the fallback operator !=, I added a printout to is_not_equal():

template<typename T, typename U>
bool is_not_equal(T&& t, U&& u)
{
    std::cout << "!"; // <== FOR TESTING PURPOSES
    return !(std::forward<T>(t) == std::forward<U>(u));
}

Here are the three tests from your example:

std::cout << (a != b) << "\n"; // #1
std::cout << (test3() != test3()) << "\n"; // #2
std::cout << (test4() != test4()) << "\n"; // #3

Concerning the first test, operator != is defined for type test, so line #1 should print:

(!==)1

Regarding the second test, operator != is not defined for test3, and test3 is not convertible to test4, so our global operator != should come into play and negate the result of the overload of operator == that takes two const test3&. Therefore, line #2 should print:

!(==)0 // operator == returns true, and is_not_equal() negates it

Finally, the third test involves two rvalue objects of type test4, for which operator != is defined (because the arguments are convertible to test4 const&). Therefore, line #3 should print:

(!=)1

And here is a live example showing that the output produced is the expected one.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!