11

I have is_callable trait defined like this:

#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

My question is how to detect templated operator() which doesn't have arguments and has only return type T

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

or

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

I know that this situations are rare, but I wanted to ask is there any way to detect presence of templated operator() with no arguments and with one or more template arguments.

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • May be this would be usefull: http://stackoverflow.com/questions/9117603/how-does-this-has-member-class-template-work/9117836#9117836 – Lol4t0 Feb 10 '12 at 16:59
  • Overload is never based on the return type. – Matthieu M. Feb 10 '12 at 17:03
  • @MatthieuM. Sorry, my bad. I accidentally mixed two different questions..:( I've edited question. I wanted to ask is there any way to detect presence of templated operator() with no arguments and with one or more template arguments. I cannot simply call operator() because there is no information how many template arguments there are and I don't know how to instantiate it, i.e, there is no way to deduce template arguments. – Goran Aranđelović Feb 10 '12 at 20:21
  • @Goran i don't understand your question. What do you want to achieve?? A class object with that `operator()` is not "callable". – Johannes Schaub - litb Feb 10 '12 at 20:56
  • @JohannesSchaub-litb Well, yes, it's not callable as any other "callable" entity but can be called is this manner: Example: x.operator()() ... And I was just curious who can I "catch" that operator. – Goran Aranđelović Feb 10 '12 at 21:00
  • @JohannesSchaub-litb Ok, I understand...it's only meaningful to "catch" templated operator()'s if they have one or more arguments which will serve for template argument deduction... Yes, question is misleading. Sorry for that. – Goran Aranđelović Feb 10 '12 at 21:10

2 Answers2

4

If you know in advance operator() is not going to be overloaded, you can try to take its address. If operator() is possibly overloaded, then a positive result would mean that there is an operator() present but a negative result would mean that either no operator() is present, or at least two overloads are.

Notice that a template will (as expected) bring several overloads of operator(). However, if you do know the number of template parameters that are not defaulted you can try taking the address of operator()<T> (for some type T that hopefully won't trigger SFINAE).

As a final note, I'd suggest not trying to spend too much time trying to inspect functors (or member functions, for the same reasons) without knowing what arguments to pass, just like what you already have. C++11 makes it very easy to write and use generic code that functions at the expression level.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • you could make a helper class that inherits from the class and implements its own operator(). Then you should be able to differentiate the ambiguous case. – Tim Seguine May 11 '16 at 18:39
2

You are trying to detect an operator() member function template with non-deduced template parameters, which isn't really "callable" at all, and also is kind of pointless - the function template should instead have a real name, because your example really misses the point of the whole operator thing. But let's solve your problem anyway.

Allow me to preface this with a plug for a library solution I'm working on, called CallableTraits (again, a work in progress).

While your case is not handled by CallableTraits, the library does employ a technique I'm about to describe to solve a very similar problem. The technique is a total hack, but it's standard compliant, and works for me on the following platforms:

  • GCC 5.2 and later
  • Clang 3.5 and later
  • Visual Studio 2015 Update 1 - basically works

Note: Visual Studio 2015 Update 2 is broken, because it incorrectly deduces std::index_sequence<I...> in partial specializations... I filed a bug report. See here for a description.

Note: If your standard library implementation doesn't have std::disjunction yet then you can use the sample implementation here instead.

I call the technique the template worm. It's the metaprogramming equivalent of spitting into a deep, dark well, just to hear how long it takes to splash.

What is a template worm?

  1. A template worm is a class that is convertible to anything and everything.
  2. Any operator expressions with a template worm operand will always evaluate to another template worm.
  3. A template worm can only be used in an unevaluated context. In other words, you can only use it when a decltype surrounds the top-level expression, just like std::declval<T>().

A template worm wiggles itself into places it isn't supposed to be, and sticks to the first concrete type it can find. In a similar fashion, a real worm will stick to the concrete on any afternoon in July.

To solve your problem, we will start with no arguments, then recursively work up to an arbitrary limit of 10. We try to make the call to the (potential) function object by trying to pass the template worm by by function-style invocation, AND by template type argument (per your requirements).

This code doesn't account for INVOKE semantics, because that takes signifcantly more code. If you need this to work with pointers-to-member-functions and pointers-to-member-data, you can roll your own implementation for that.

I might not have covered all the operators, and I might not have implemented them all correctly, but you'll see the point.

One last thing:

I know of one catch. The return type can't depend on a dependent name (other than member operators).

Edit: Also, invocation/template instantiation needs to be SFINAE-friendly (i.e. no static_asserts).

Without further ado, here is your standalone solution (although you might wish you hadn't asked):

#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() {}
Barrett Adair
  • 1,306
  • 10
  • 24