web-dev-qa-db-ja.com

C ++オブジェクトが呼び出し可能かどうかを確認します

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

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

33
Antoine

この特性はあなたが望むことをしていると思います。オーバーロードされていても、テンプレート化されていても、あらゆる種類のシグネチャで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を返す)のは簡単です。上記のクラスの名前を_is_callable_impl_に変更すると、次のように記述できます。

_template<typename T>
struct is_callable
    : std::conditional<
        std::is_class<T>::value,
        is_callable_impl<T>,
        std::false_type
    >::type
{ };
_
34
jrok

ここでの回答は役に立ちましたが、オブジェクトであるか古典的な関数であるかに関係なく、何かが呼び出可能かどうかを特定できるものが必要でした。 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*)

これが誰かを助けることを願っています!

10
Charphacy

これは、ファンクターの呼び出し演算子のシグネチャを知らなくても機能するC++ 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");
}
9
Andy Prowl

もちろん、他にもいくつかの答えがあり、それらは有用ですが、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を使用して呼び出し可能な式を作成できます。

4
monkey0506

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

以下は、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);
};

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

3
EHuhtala

これは、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 { };
1
Keijo

これが別の実装です。

それは無料の関数のために_std::is_function_テンプレートを利用します。

クラスの場合、 Member Detector Idiom のようなものを使用します。 Tに呼び出し演算子がある場合、_callable_2_には複数のoperator()が含まれます。これにより、最初の_can_call_関数はdecltype(&callable_2<T>::operator())のあいまいさの失敗により(SFINAEを介して)破棄され、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>>;
_
1

C++ 17は std :: is_invocable と友達をもたらします。

この回答 C++ 14でエミュレートする方法についての解決策も示されています。

1
logicor