Подтвердить что ты не робот

Узнайте, может ли объект C++ вызываться

Можно ли написать черту типа, скажем, is_callable<T> которая сообщает, определен ли объект operator()? Легко, если аргументы оператора вызова известны заранее, но не в общем случае. Я хочу, чтобы признак возвращал true, если и только если определен хотя бы один перегруженный оператор вызова.

Этот вопрос связан и имеет хороший ответ, но он работает не для всех типов (только для типов int -convertible). Кроме того, std::is_function работает, но только для надлежащих функций C++, а не для функторов. Я ищу более общее решение.

4b9b3361

Ответ 1

Я думаю, что эта черта делает то, что вы хотите. Он обнаруживает operator() с любой подписью, даже если он перегружен, а также если он шаблонизирован:

template<typename T>
struct is_callable {
private:
    typedef char(&yes)[1];
    typedef char(&no)[2];

    struct Fallback { void operator()(); };
    struct Derived : T, Fallback { };

    template<typename U, U> struct Check;

    template<typename>
    static yes test(...);

    template<typename C>
    static no test(Check<void (Fallback::*)(), &C::operator()>*);

public:
    static const bool value = sizeof(test<Derived>(0)) == sizeof(yes);
};

Принцип основан на идиоме Member Detector. На самом деле, он не будет компилироваться, если вы передадите ему неклассный тип, но это не должно быть трудно исправить, я просто оставил это для краткости. Вы также можете расширить его, чтобы сообщить true для функций.

Конечно, это не дает вам никакой информации о подписи (ях) operator() вообще, но я считаю, что это не то, что вы просили, верно?

РЕДАКТИРОВАТЬ для Klaim:

Это достаточно просто, чтобы заставить его работать (возвращать false) с не классовыми типами. Если вы переименуете вышеупомянутый класс в is_callable_impl, вы можете написать это, например:

template<typename T>
struct is_callable
    : std::conditional<
        std::is_class<T>::value,
        is_callable_impl<T>,
        std::false_type
    >::type
{ };

Ответ 2

Вот возможное решение с использованием С++ 11, которое работает без необходимости знать сигнатуру оператора вызова для функторов, но только до тех пор, пока функтор не имеет более одной перегрузки operator():

#include <type_traits>

template<typename T, typename = void>
struct is_callable : std::is_function<T> { };

template<typename T>
struct is_callable<T, typename std::enable_if<
    std::is_same<decltype(void(&T::operator())), void>::value
    >::type> : std::true_type { };

Вот как вы бы это использовали:

struct C
{
    void operator () () { }
};

struct NC { };

struct D
{
    void operator () () { }
    void operator () (int) { }
};

int main()
{
    static_assert(is_callable<C>::value, "Error");
    static_assert(is_callable<void()>::value, "Error");

    auto l = [] () { };
    static_assert(is_callable<decltype(l)>::value, "Error");

    // Fires! (no operator())
    static_assert(is_callable<NC>::value, "Error");

    // Fires! (several overloads of operator ())
    static_assert(is_callable<D>::value, "Error");
}

Ответ 3

Ответы здесь были полезными, но я пришел сюда, желая что-то, что могло бы также определить, было ли что-то вызываемым, независимо от того, было ли это объектом или классической функцией. jrok answer к этому аспекту проблемы, увы, не сработало, потому что std::conditional действительно оценивает типы обоих рук!

Итак, вот решение:

// Note that std::is_function says that pointers to functions
// and references to functions aren't functions, so we'll make our 
// own is_function_t that pulls off any pointer/reference first.

template<typename T>
using remove_ref_t = typename std::remove_reference<T>::type;

template<typename T>
using remove_refptr_t = typename std::remove_pointer<remove_ref_t<T>>::type;

template<typename T>
using is_function_t = typename std::is_function<remove_refptr_t<T>>::type;

// We can't use std::conditional because it (apparently) must determine
// the types of both arms of the condition, so we do it directly.

// Non-objects are callable only if they are functions.

template<bool isObject, typename T>
struct is_callable_impl : public is_function_t<T> {};

// Objects are callable if they have an operator().  We use a method check
// to find out.

template<typename T>
struct is_callable_impl<true, T> {
private:
    struct Fallback { void operator()(); };
    struct Derived : T, Fallback { };

    template<typename U, U> struct Check;

    template<typename>
    static std::true_type test(...);

    template<typename C>
    static std::false_type test(Check<void (Fallback::*)(), &C::operator()>*);

public:
    typedef decltype(test<Derived>(nullptr)) type;
};


// Now we have our final version of is_callable_t.  Again, we have to take
// care with references because std::is_class says "No" if we give it a
// reference to a class.

template<typename T>
using is_callable_t = 
    typename is_callable_impl<std::is_class<remove_ref_t<T>>::value,
                              remove_ref_t<T> >::type;

Но в конце, для моего приложения, я действительно хотел просто знать, можно ли сказать f() (т.е. называть его без аргументов), поэтому вместо этого я пошел с чем-то гораздо более простым.

template <typename T>
constexpr bool noarg_callable_impl(
    typename std::enable_if<bool(sizeof((std::declval<T>()(),0)))>::type*)
{
    return true;
}

template<typename T>
constexpr bool noarg_callable_impl(...)
{
    return false;
}

template<typename T>
constexpr bool is_noarg_callable()
{
    return noarg_callable_impl<T>(nullptr);
}

На самом деле я пошел еще дальше. Я знал, что функция должна была возвращать int, поэтому вместо того, чтобы просто проверить, что я могу ее назвать, я также проверил тип возвращаемого значения, изменив enable_if на:

    typename std::enable_if<std::is_convertible<decltype(std::declval<T>()()),
                                                int>::value>::type*)

Надеюсь, это поможет кому-то!

Ответ 4

Примечание. Предполагается, что конструктор по умолчанию действителен для типа вашей проверки. Не уверен, как обойти это.

Следующее, похоже, работает, если оно вызываемо с 0 аргументами. Есть ли что-то в реализации is_function, которая может помочь расширить это до 1 или более аргументов?

template <typename T>
struct is_callable {
    // Types "yes" and "no" are guaranteed to have different sizes,
    // specifically sizeof(yes) == 1 and sizeof(no) == 2.
    typedef char yes[1];
    typedef char no[2];

    template <typename C>
    static yes& test(decltype(C()())*);

    template <typename>
    static no& test(...);

    // If the "sizeof" the result of calling test<T>(0) would be equal to the     sizeof(yes),
    // the first overload worked and T has a nested type named foobar.
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};   

Если вы знаете тип аргумента (даже если он является параметром шаблона), следующее будет работать для 1 аргумента, и я предполагаю, что от него можно было бы легко перейти:

template <typename T, typename T2>
struct is_callable_1 {
    // Types "yes" and "no" are guaranteed to have different sizes,
    // specifically sizeof(yes) == 1 and sizeof(no) == 2.
    typedef char yes[1];
    typedef char no[2];

    template <typename C>
    static yes& test(decltype(C()(T2()))*);

    template <typename, typename>
    static no& test(...);

    // If the "sizeof" the result of calling test<T>(0) would be equal to the     sizeof(yes),
    // the first overload worked and T has a nested type named foobar.
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};

Edit здесь - это модификация, которая обрабатывает случай, когда конструктор по умолчанию недоступен.

Ответ 5

Конечно, есть несколько других ответов, и они полезны, но ни один из них, похоже, не охватывает все варианты использования AFAICT. Заимствуя из этих ответов и эту возможную реализацию std:: is_function, я создал версию, которая охватывает все возможные варианты использования, о которых я мог думать. Это довольно длинная, но очень полная функция (Demo).

template<typename T, typename U = void>
struct is_callable
{
    static bool const constexpr value = std::conditional_t<
        std::is_class<std::remove_reference_t<T>>::value,
        is_callable<std::remove_reference_t<T>, int>, std::false_type>::value;
};

template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(*)(Args...), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(&)(Args...), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(*)(Args......), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(&)(Args......), U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)volatile, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const volatile, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)volatile, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const volatile, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)&, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)volatile&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const volatile&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)&, U> : std::true_type {};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)volatile&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const volatile&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)volatile&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args...)const volatile&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)volatile&&, U> : std::true_type{};
template<typename T, typename U, typename ...Args>
struct is_callable<T(Args......)const volatile&&, U> : std::true_type{};

template<typename T>
struct is_callable<T, int>
{
private:
    using YesType = char(&)[1];
    using NoType = char(&)[2];

    struct Fallback { void operator()(); };

    struct Derived : T, Fallback {};

    template<typename U, U>
    struct Check;

    template<typename>
    static YesType Test(...);

    template<typename C>
    static NoType Test(Check<void (Fallback::*)(), &C::operator()>*);

public:
    static bool const constexpr value = sizeof(Test<Derived>(0)) == sizeof(YesType);
};

Это корректно работает с типами некласса (конечно, возвращает false), типы функций (< T() > ), типы указателей функций, типы ссылочных типов, типы классов функтора, выражения связи, типы лямбда и т.д. Это работает корректно, даже если конструктор класса является закрытым и/или не дефолтным, и даже если оператор() перегружен. Это возвращает false для указателей функций-членов по дизайну, потому что они не подлежат вызову, но вы можете использовать bind для создания вызываемого выражения.

Ответ 6

Вот еще одна реализация.

Он использует шаблон std::is_function для бесплатных функций.

Для классов он использует что-то похожее на Идентификатор идентификатора участника. Если T имеет оператор вызова, callable_2 будет содержать более одного operator(). Это приведет к отбрасыванию первой функции can_call (через SFINAE) из-за ошибки двусмысленности в decltype(&callable_2<T>::operator()), а вторая функция can_call вернет true. Если T не имеет оператора вызова, будет использоваться первая функция can_call (из-за правил разрешения перегрузки).

namespace impl
{
struct callable_1 { void operator()(); };
template<typename T> struct callable_2 : T, callable_1 { };

template<typename T>
static constexpr bool can_call(decltype(&callable_2<T>::operator())*) noexcept { return false; }

template<typename>
static constexpr bool can_call(...) noexcept { return true; }

template<bool is_class, typename T>
struct is_callable : public std::is_function<T> { };

template<typename T> struct is_callable<false, T*> : public is_callable<false, T> { };
template<typename T> struct is_callable<false, T* const> : public is_callable<false, T> { };
template<typename T> struct is_callable<false, T* volatile> : public is_callable<false, T> { };
template<typename T> struct is_callable<false, T* const volatile> : public is_callable<false, T> { };

template<typename T>
struct is_callable<true, T> : public std::integral_constant<bool, can_call<T>(0)> { };
}

template<typename T>
using is_callable = impl::is_callable<std::is_class<std::remove_reference_t<T>>::value,
                                    std::remove_reference_t<T>>;

Ответ 7

Это аккуратный и короткий трюк для определения, может ли T вызываться. Он идет в русле, первоначально предложенном Уолтером Е. Брауном на CPPCON'14 в его докладе о современном метапрограммировании шаблонов.

template <class... >
using void_t = void;

template <class T>
using has_opr_t = decltype(&T::operator());

template <class T, class = void>
struct is_callable : std::false_type { };

template <class T>
struct is_callable<T, void_t<has_opr_t<typename std::decay<T>::type>>> : std::true_type { };