Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected result of std::is_invocable over a template type

I have an if constexpr checking that a type is equality-comparable with itself. I use std::is_invocable_v<std::equal_to<>, T, T>.

However, when T is a vector of incomparable structs, the snippet falsely returns True. Is there a deep reason for it or is it a compiler bug?

Minimal example follows.

#include <type_traits>
#include <iostream>
#include <vector>

class TNonComparable{};

int main()
{
    std::cout << std::is_invocable_v<std::equal_to<>, TNonComparable, TNonComparable> << "\n";
    // 0

    std::cout << std::is_invocable_v<
            std::equal_to<>,
            std::vector<TNonComparable>,
            std::vector<TNonComparable>
        > << "\n";
    // 1

    std::vector<TNonComparable> vec;
    // vec == vec;
    // (expected) compilation error
}

I checked the output at Godbolt's, it is the same for all recent versions of g++ and clang.

like image 466
Ivan Smirnov Avatar asked Sep 09 '20 13:09

Ivan Smirnov


3 Answers

I think it's very important to look at what std::is_invocable does:

Determines whether Fn can be invoked with the arguments ArgTypes.... Formally, determines whether INVOKE(declval<Fn>(), declval<ArgTypes>()...) is well formed when treated as an unevaluated operand, where INVOKE is the operation defined in Callable.

Emphasis mine.

The important part to note here is that std::equal_to<> used inside std::is_invocable will never be evaluated because it's a unevaluated operand. This means that it only checks if operator== exists at all, which it does for std::vector<>, not if it would compile in an evaluated context.

like image 54
Hatted Rooster Avatar answered Oct 18 '22 20:10

Hatted Rooster


I think, this is correct behavior.

In the first std::is_invokable_v checks for the presence of the operator== in the TNonComparable type. It is not present - so the result is 0.

In the second case std::is_invokable_v checks for the equality operator of the std::vector, which is present and may be invoked. But if do try to invoke it, it won't be able to compile because the TNonComparable type doesn't have operator==. But before you don't try to use it, it won't generate an error.

Maybe, in second case you should check for value_type of the std::vector:

std::cout << std::is_invocable_v<
        std::equal_to<>,
        std::vector<TNonComparable>::value_type,
        std::vector<TNonComparable>::value_type
    > << "\n";
// 0
like image 32
Vasilij Avatar answered Oct 18 '22 21:10

Vasilij


Had some time and I've wrote workaround tool

namespace detail
{
    template<typename T, typename=void>
    struct has_value_type : std::false_type {};

    template<typename T>
    struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};

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

    template<typename T, typename U>
    struct is_pair<std::pair<T, U>, void> : std::true_type {};

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

    template<typename ...Args>
    struct is_tuple<std::tuple<Args...>, void> : std::true_type {};
}

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

template<typename T>
struct has_euqual_operator<
        T, 
        std::void_t<
            std::enable_if_t<
                !detail::is_pair<T>::value 
                && !detail::has_value_type<T>::value 
                && !detail::is_tuple<T>::value>,
            decltype(std::declval<T>() == std::declval<T>())
        >
    > : std::true_type 
{};

template<typename T>
struct has_euqual_operator<
        T, 
        std::enable_if_t<has_euqual_operator<typename T::value_type>::value>
    > : std::true_type 
{};

template<typename T>
struct has_euqual_operator<
        T, 
        std::enable_if_t<has_euqual_operator<typename T::first_type>::value
            && has_euqual_operator<typename T::second_type>::value
        >
    > : std::true_type 
{};

template<typename ...Args>
struct has_euqual_operator<
        std::tuple<Args...>, 
        std::enable_if_t<(... && has_euqual_operator<Args>::value)>
    > : std::true_type 
{};

https://godbolt.org/z/rcejhx

like image 45
Marek R Avatar answered Oct 18 '22 20:10

Marek R