37

is_callable<T>オブジェクトにoperator()定義されているかどうかを示すタイプ特性を書くことは可能ですか?呼び出し演算子への引数が事前にわかっていれば簡単ですが、一般的な場合はそうではありません。少なくとも1つのオーバーロードされた呼び出し演算子が定義されている場合にのみ、トレイトがtrueを返すようにします。

この質問は関連していて良い答えがありますが、すべてのタイプで機能するわけではありません(int-convertibleタイプでのみ)。また、std::is_function動作しますが、ファンクターではなく、適切なC++関数でのみ動作します。より一般的な解決策を探しています。

4

8 に答える 8

38

この特性はあなたが望むことをしていると思います。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);
};

原則は、メンバー検出イディオムに基づいています。そのままでは、クラス以外の型を渡すとコンパイルに失敗しますが、修正するのは難しいことではありません。簡潔にするために省略しました。関数に対してtrueを報告するように拡張することもできます。

もちろん、それはあなたに何の署名についての情報も与えoperator()ません、しかし私はそれがあなたが求めたものではないと思いますよね?

Klaimの編集:

falseクラス以外のタイプでも機能する(return)のは簡単です。上記のクラスの名前をに変更するとis_callable_impl、次のように記述できます。

template<typename T>
struct is_callable
    : std::conditional<
        std::is_class<T>::value,
        is_callable_impl<T>,
        std::false_type
    >::type
{ };
于 2013-03-13T21:24:07.387 に答える
10

これは、ファンクターの呼び出し演算子のシグネチャを知らなくても機能するC ++ 11を使用した可能な解決策ですが、ファンクターに次のオーバーロードが1つ以上ない場合に限ります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");
}
于 2013-03-13T19:19:55.107 に答える
10

ここでの答えは役に立ちましたが、オブジェクトであるかクラシック関数であるかに関係なく、何かが呼び出可能かどうかを検出できるものが必要でした。 問題のこの側面に対するjrokの答え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*)

これが誰かに役立つことを願っています!

于 2014-09-16T00:36:57.127 に答える
7

C ++ 17は、 std::is_invocableとその仲間をもたらします。

この回答は、C++14でそれをエミュレートする方法についての解決策も提供しました。

于 2019-04-22T23:14:22.537 に答える
4

もちろん、他にもいくつかの答えがあり、それらは有用ですが、AFAICTのすべてのユースケースを網羅しているわけではないようです。それらの答えとstd::is_functionのこの可能な実装から借りて、私は考えられるすべての可能なユースケースをカバーするバージョンを作成しました。ちょっと長いですが、非常に機能が充実しています(デモ)。

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()>)、関数ポインター型、関数参照型、関数クラス型、バインド式、ラムダ型などで正しく機能します。クラスコンストラクターがプライベートおよび/またはデフォルトではない場合、およびoperator()がオーバーロードされている場合でも。これは、呼び出し可能ではないため、設計上メンバー関数ポインターに対してfalseを返しますが、bindを使用して呼び出し可能な式を作成できます。

于 2016-03-03T03:12:51.813 に答える
3

注:これらは、デフォルトのコンストラクターがチェックするタイプに対して有効であることを前提としています。それを回避する方法がわからない。

引数が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);
};

ここでの編集は、デフォルトのコンストラクターが使用できない場合を処理する変更です。

于 2013-03-13T19:19:56.587 に答える
3

これは、Tが呼び出し可能かどうかを見つけるための巧妙で短いトリックです。これは、CPPCON'14でWalter E. Brownが、最新のテンプレートメタプログラミングに関する講演で最初に提案した方針に沿ったものです。

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 { };
于 2019-03-04T10:09:14.623 に答える
1

これが別の実装です。

それはstd::is_function自由な機能のためのテンプレートを利用します。

クラスの場合、 MemberDetectorIdiomに似たものを使用します。T呼び出し演算子がある場合は、callable_2複数のが含まれますoperator()。これにより、のcan_callあいまいさの失敗により最初の関数が(SFINAEを介して)破棄されdecltype(&callable_2<T>::operator())、2番目の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>>;
于 2016-07-31T23:02:52.877 に答える