11

我有这样定义的 is_callable 特征:

#ifndef IS_CALLABLE_HPP
#define IS_CALLABLE_HPP

#include <type_traits>

namespace is_callable_detail
{
    struct no   {};
    struct yes  { no x[2]; };

    template<bool CallableArgs, typename Callable, typename ReturnType, typename ...Args>
    struct check_return
    {
        static const bool value = std::is_convertible<decltype(std::declval<Callable>()(std::declval<Args>()...)), ReturnType>::value;
    };

    template<typename Callable, typename ReturnType, typename ...Args>
    struct check_return<false, Callable, ReturnType, Args...>
    {
        static const bool value = false;
    };
}

template<typename Callable, typename Function>
struct is_callable;

template<typename Callable, typename ReturnType, typename ...Args>
struct is_callable<Callable, ReturnType(Args...)>
{
    private:
        template<typename T>
        static is_callable_detail::yes check(decltype(std::declval<T>()(std::declval<Args>()...)) *);
        template<typename T>
        static is_callable_detail::no  check(...);

        static const bool value_args = sizeof(check<Callable>(nullptr)) == sizeof(is_callable_detail::yes);
        static const bool value_return = is_callable_detail::check_return<value_args, Callable, ReturnType, Args...>::value;
    public:
        static const bool value = value_args && value_return;
};

#endif // IS_CALLABLE_HPP

我的问题是如何检测没有参数且只有返回类型 T 的模板化 operator()

template<typename T>
T operator()()
{
  // ...
}

或者

template<typename T, typename U>
auto operator()() -> decltype(std::declval<T>() + std::declval<U>())
{
  // ...
}

我知道这种情况很少见,但我想问是否有任何方法可以检测没有参数且带有一个或多个模板参数的模板化 operator() 的存在。

4

2 回答 2

4

如果你事先知道operator()不会超载,你可以尝试取它的地址。如果可能operator()重载,则肯定结果将意味着存在,而否定结果将意味着不存在,或者至少存在两个重载。operator()operator()

请注意,模板将(如预期的那样)带来多个operator(). 但是,如果您确实知道未默认的模板参数的数量,您可以尝试获取(对于希望不会触发 SFINAE 的某些类型)operator()<T>地址。T

最后一点,我建议不要在不知道要传递什么参数的情况下花费太多时间来检查函子(或成员函数,出于同样的原因),就像您已经拥有的一样。C++11 使得编写和使用在表达式级别起作用的通用代码变得非常容易。

于 2012-02-11T07:02:14.877 回答
2

您正在尝试检测operator()具有非推导模板参数的成员函数模板,这根本不是真正的“可调用”,而且有点毫无意义 - 函数模板应该有一个真实的名称,因为您的示例真的错过了整件事的重点operator。但无论如何,让我们解决您的问题。

请允许我用一个我正在开发的库解决方案的插件作为序言,称为CallableTraits(同样,正在进行中的工作)。

虽然您的案例不是由 CallableTraits 处理的,但该库确实采用了我将要描述的一种技术来解决一个非常相似的问题。该技术完全是 hack,但它符合标准,并且适用于以下平台:

  • GCC 5.2 及更高版本
  • Clang 3.5 及更高版本
  • Visual Studio 2015 Update 1 - 基本上可以工作

注意:Visual Studio 2015 Update 2 已损坏,因为它错误地推断出std::index_sequence<I...>部分专业化......我提交了一个错误报告。有关说明,请参见此处

注意:如果您的标准库实现还没有,那么您可以使用此处std::disjunction的示例实现。

我称这种技术为模板蠕虫。这是一种元编程,相当于向一口又深又黑的井里吐口水,只是为了听听溅起需要多长时间。

什么是模板蠕虫?

  1. 模板蠕虫是一个可以转换为任何东西的类。
  2. 任何带有模板蠕虫操作数的运算符表达式将始终计算为另一个模板蠕虫。
  3. 模板蠕虫只能在未评估的上下文中使用。换句话说,您只能在 adecltype包围顶级表达式时使用它,就像std::declval<T>().

模板蠕虫会在不应该出现的地方摆动自己,并坚持它可以找到的第一个具体类型。以类似的方式,一条真正的蠕虫会在七月的任何一个下午粘在混凝土上。

为了解决您的问题,我们将从无参数开始,然后递归地工作到任意限制 10。我们尝试通过函数式调用传递模板蠕虫来调用(潜在)函数对象,并通过模板类型参数(根据您的要求)。

此代码不考虑INVOKE语义,因为这需要更多的代码。如果您需要它与指向成员函数的指针和指向成员数据的指针一起使用,您可以为此推出自己的实现。

我可能没有涵盖所有的运算符,也可能没有正确地实现它们,但你会明白这一点的。

最后一件事:

我知道一个捕获。返回类型不能依赖于依赖名称(成员运算符除外)。

编辑:另外,调用/模板实例化需要对 SFINAE 友好(即没有static_asserts)。

事不宜迟,这是您的独立解决方案(尽管您可能希望自己没有问过):

#include <utility>
#include <type_traits>

namespace detail {

    //template_worm CANNOT be used in evaluated contexts
    struct template_worm {

        template<typename T>
        operator T& () const;

        template<typename T>
        operator T && () const;

        template_worm() = default;

#ifndef _MSC_VER

        // MSVC doesn't like this... because it can deduce void?
        // Whatever, we can do without it on Windows
        template<typename... T>
        template_worm(T&&...);

#endif //_MSC_VER

        template_worm operator+() const;
        template_worm operator-() const;
        template_worm operator*() const;
        template_worm operator&() const;
        template_worm operator!() const;
        template_worm operator~() const;
        template_worm operator()(...) const;
    };

#define TEMPLATE_WORM_BINARY_OPERATOR(...)                                 \
                                                                           \
    template<typename T>                                                   \
    constexpr inline auto                                                  \
    __VA_ARGS__ (template_worm, T&&) -> template_worm {                    \
        return template_worm{};                                            \
    }                                                                      \
                                                                           \
    template<typename T>                                                   \
    constexpr inline auto                                                  \
    __VA_ARGS__ (T&&, template_worm) -> template_worm {                    \
        return template_worm{};                                            \
    }                                                                      \
                                                                           \
    constexpr inline auto                                                  \
    __VA_ARGS__ (template_worm, template_worm) -> template_worm {          \
        return template_worm{};                                            \
    }                                                                      \
    /**/

    TEMPLATE_WORM_BINARY_OPERATOR(operator+)
    TEMPLATE_WORM_BINARY_OPERATOR(operator-)
    TEMPLATE_WORM_BINARY_OPERATOR(operator/)
    TEMPLATE_WORM_BINARY_OPERATOR(operator*)
    TEMPLATE_WORM_BINARY_OPERATOR(operator==)
    TEMPLATE_WORM_BINARY_OPERATOR(operator!=)
    TEMPLATE_WORM_BINARY_OPERATOR(operator&&)
    TEMPLATE_WORM_BINARY_OPERATOR(operator||)
    TEMPLATE_WORM_BINARY_OPERATOR(operator|)
    TEMPLATE_WORM_BINARY_OPERATOR(operator&)
    TEMPLATE_WORM_BINARY_OPERATOR(operator%)
    TEMPLATE_WORM_BINARY_OPERATOR(operator,)
    TEMPLATE_WORM_BINARY_OPERATOR(operator<<)
    TEMPLATE_WORM_BINARY_OPERATOR(operator>>)
    TEMPLATE_WORM_BINARY_OPERATOR(operator<)
    TEMPLATE_WORM_BINARY_OPERATOR(operator>)

    template<std::size_t Ignored>
    using worm_arg = template_worm const &;

    template<typename T>
    struct success {};

    struct substitution_failure {};

    template<typename F, typename... Args>
    struct invoke_test {

        template<typename T, typename... Rgs>
        auto operator()(T&& t, Rgs&&... rgs) const ->
            success<decltype(std::declval<T&&>()(std::forward<Rgs>(rgs)...))>;

        auto operator()(...) const->substitution_failure;

        static constexpr int arg_count = sizeof...(Args);
    };

    // force_template_test doesn't exist in my library
    // solution - it exists to please OP
    template<typename... Args>
    struct force_template_test {

        template<typename T>
        auto operator()(T&& t) const ->
            success<decltype(std::declval<T&&>().template operator()<Args...>())>;

        auto operator()(...) const->substitution_failure;
    };

    template<typename T, typename... Args>
    struct try_invoke {

        using test_1 = invoke_test<T, Args...>;

        using invoke_result = decltype(test_1{}(
            ::std::declval<T>(),
            ::std::declval<Args>()...
            ));

        using test_2 = force_template_test<Args...>;

        using force_template_result = decltype(test_2{}(std::declval<T>()));

        static constexpr bool value =
            !std::is_same<invoke_result, substitution_failure>::value
            || !std::is_same<force_template_result, substitution_failure>::value;

        static constexpr int arg_count = test_1::arg_count;
    };

    template<typename T>
    struct try_invoke<T, void> {
        using test = invoke_test<T>;
        using result = decltype(test{}(::std::declval<T>()));
        static constexpr bool value = !std::is_same<result, substitution_failure>::value;
        static constexpr int arg_count = test::arg_count;
    };

    template<typename U, std::size_t Max, typename = int>
    struct min_args;

    struct sentinel {};

    template<typename U, std::size_t Max>
    struct min_args<U, Max, sentinel> {
        static constexpr bool value = true;
        static constexpr int arg_count = -1;
    };

    template<typename U, std::size_t Max, std::size_t... I>
    struct min_args<U, Max, std::index_sequence<I...>> {

        using next = typename std::conditional<
            sizeof...(I)+1 <= Max,
            std::make_index_sequence<sizeof...(I)+1>,
            sentinel
        >::type;

        using result_type = std::disjunction<
            try_invoke<U, worm_arg<I>...>,
            min_args<U, Max, next>
        >;

        static constexpr bool value = result_type::value;
        static constexpr int arg_count = result_type::arg_count;
    };

    template<typename U, std::size_t Max>
    struct min_args<U, Max, void> {

        using result_type = std::disjunction<
            try_invoke<U, void>,
            min_args<U, Max, std::make_index_sequence<1>>
        >;

        static constexpr int arg_count = result_type::arg_count;
        static constexpr bool value = result_type::value;
    };

    template<typename T, std::size_t SearchLimit>
    using min_arity = std::integral_constant<int,
        min_args<T, SearchLimit, void>::arg_count>;
}

// Here you go.
template<typename T>
using is_callable = std::integral_constant<bool,
    detail::min_arity<T, 10>::value >= 0>;

// This matches OP's first example.
struct Test1 {

    template<typename T>
    T operator()() {
        return{};
    }
};

// Yup, it's "callable", at least by OP's definition...
static_assert(is_callable<Test1>::value, "");

// This matches OP's second example.
struct Test2 {

    template<typename T, typename U>
    auto operator()() -> decltype(std::declval<T>() + std::declval<U>()) {
        return{};
    }
};

// Yup, it's "callable", at least by OP's definition...
static_assert(is_callable<Test2>::value, "");

// ints aren't callable, of course
static_assert(!is_callable<int>::value, "");

int main() {}
于 2016-04-15T05:38:21.350 回答