Как вы можете определить, является ли результат вызова действительным - PullRequest
0 голосов
/ 03 апреля 2020

Я пытаюсь создать признак, который обнаруживает, имеет ли тип Apply правильный результат при вызове с двумя аргументами. Я ожидаю, что static_assert в коде не попадет, потому что результат применения является допустимым (с делением float). Почему это утверждение срабатывает, и как бы я изменил черту таким образом, чтобы все допустимые перегрузки для применения были определены как true_type или constexpr bool true.

#include <type_traits>
#include <functional>

struct Apply
{
    template<typename T1, typename T2>
    float apply(const T1& a, const T2& b) const
    {
        return a / b;
    }
};

struct ApplyInvoker
{
    Apply a;

    template<typename... Args>
    auto operator()(Args&&... args)
    {
        return a.apply(std::forward<Args>(args)...);
    }
};

template <class Void, class... T>
struct ValidCall : std::false_type
{ 
};
template <class... T>
struct ValidCall<
    std::void_t<decltype(std::invoke(std::declval<ApplyInvoker>(), std::declval<T>()...))>, 
    T...> 
    : std::true_type
{ 
};

template<typename T1, typename T2>
constexpr bool CanApply = ValidCall<void, T1, T2>::value;

int main()
{
    static_assert(CanApply<float, float>);
}

1 Ответ

1 голос
/ 03 апреля 2020

Вы используете свой шаблон как:

template<typename T1, typename T2>
constexpr bool CanApply = ValidCall<T1, T2>::value;

Но основной объявлен как:

template <class Void, class... T>
struct ValidCall;

Этот первый параметр шаблона называется Void, потому что он имеет , чтобы быть void - это то, с чем сопоставляется специализация. Таким образом, вы должны сделать это следующим образом:

template<typename T1, typename T2>
constexpr bool CanApply = ValidCall<void, T1, T2>::value;
//                                  ^~~~

Но также это C ++ 17, и у нас есть для этого черта типа:

template<typename T1, typename T2>
constexpr bool CanApply = std::is_invocable_v<ApplyInvoker, T1, T2>;

Проблема в том, что ни Apply, ни ApplyInvoker на самом деле не работают с чертами типа. Apply объявляет себя вызываемым с любыми двумя аргументами - иначе обнаружить невозможно. ApplyInvoker объявляет себя вызываемым с любым количеством аргументов - но таким образом, что это приведет к серьезной ошибке компиляции, если вы попытаетесь выяснить с неправильным набором из них. Оба этих типа мы бы назвали SFINAE недружественными - они не дружат с чертами типов, они не тестируемы.

Если вы хотите, чтобы они действительно были тестируемыми, вам нужно переписать оба типа следующим образом :

struct Apply
{
    template<typename T1, typename T2>
    float apply(const T1& a, const T2& b) const
        -> decltype(float(a / b))
    {
        return a / b;
    }
};

struct ApplyInvoker
{
    Apply a;

    template <typename... Args>
    auto operator()(Args&&... args)
        -> decltype(a.apply(std::forward<Args>(args)...)
    {
        return a.apply(std::forward<Args>(args)...);
    }
};

или что-то подобное.

...