C++ meta function that determines if a type is callable for supplied arguments
Asked Answered
E

3

6

I am trying to implement a C++ template meta function that determines if a type is callable from the method input arguments.

i.e. for a function void foo(double, double) the meta function would return true for callable_t<foo, double, double>, true for callable_t<foo, int, int> (due to compiler doing implicit cast) and false for anything else such as wrong number of arguments callable_t<foo, double>.

My attempt is as follows, however it fails for any function that returns anything other than void and I can't seem to fix it.

I am new to template reprogramming so any help would be appreciated.

#include <iostream>
#include <type_traits>
#include <utility>
#include <functional>

namespace impl
{

template <typename...>
struct callable_args
{
};

template <class F, class Args, class = void>
struct callable : std::false_type
{
};

template <class F, class... Args>
struct callable<F, callable_args<Args...>, std::result_of_t<F(Args...)>> : std::true_type
{
};

}

template <class F, class... Args>
struct callable : impl::callable<F, impl::callable_args<Args...>>
{
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, Args...>::value;


int main()
{
    {
        using Func = std::function<void()>;
        auto result = callable_v<Func>;
        std::cout << "test 1 (should be 1) = " << result << std::endl;
    }

    {
        using Func = std::function<void(int)>;
        auto result = callable_v<Func, int>;
        std::cout << "test 2 (should be 1) = " << result << std::endl;
    }

    {
        using Func = std::function<int(int)>;
        auto result = callable_v<Func, int>;
        std::cout << "test 3 (should be 1) = " << result << std::endl;
    }

    std::getchar();

    return EXIT_SUCCESS;
}

I am using a compiler that supports C++ 14.

Ease answered 29/11, 2016 at 13:52 Comment(5)
What about callable_t<foo, float, float>?Allman
@NathanOliver, any arguments that the compiler would deduce are callable (albeit with a warning) should be valid, so callable_t<foo, float, float> would be ok if foo is defined as say foo(int, int) or foo(double, double) or foo(float, float) but not foo(custom_type, custom_type) where custom_type cannot be converted implicity.Ease
@Marco A. Sadly not :-(Ease
@Ease (edited msg) If your compiler also supports some C++17 features I'd recommend is_callable. Otherwise here's a hackish fix for your code. Be advised that this code has many flaws like avoiding the is_convertible conversions entirely and template arguments well-formedness.Suffuse
@Marco A, thanks I tried that already, but looking here at std::void_t it seems my c++ 14 compiler has a problem with the simple void_t definition. Using the suggested implementation for void_t in the link makes your example work on my compiler! I'd love to hear more about the flaws in my code in order to learn. I am very much at the start of my journey into TMP.Ease
C
2

The shorten use of std::result_of to do what you want could look as follows:

template <class T, class, class... Args>
struct callable: std::false_type {
};

template <class T, class... Args>
struct callable<T, decltype(std::result_of_t<T(Args...)>(), void()), Args...>:std::true_type {
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, void, Args...>::value;

[live demo]

you need to remember that type returned by result_of is always the result type of a function you pass to this trait by type. To let your sfinae work you need a method to change this type to void in every possible situation. You can accomplish it by using the trick with decltype (decltype(std::result_of_t<T(Args...)>(), void())).

Edit:

To elaborate the thread from comments about the possible drawbacks of the solution. The std::result_of_t<T(Args...)> type don't need to be equipped with a default non-parametric constructor and as such the sfinae may cause false negative result of callable_v for function that result in this kind of types. In comments I proposed a workaround for the issue that does not really solve the problem or actually generate a new one:

decltype(std::declval<std::result_of_t<T(Args...)>*>(), void())

Intention of this code was to make sfinae work as in previously proposed solution but in case of non-constructable types to create an easy to construct (I thought) object of pointer to given type... In this reasoning I didn't take into consideration the types that one cannot create a pointer to e.g. references. This one again can be workaround by using some additional wrapper class:

decltype(std::declval<std::tuple<std::result_of_t<T(Args...)>>*>(), void())

or by decaying the result type:

decltype(std::declval<std::decay_t<std::result_of_t<T(Args...)>>*>(), void())

but I think it might not be worth it and maybe use of void_t is actually a more straightforward solution:

template <class...>
struct voider {
    using type = void;
};

template <class... Args>
using void_t = typename voider<Args...>::type;

template <class T, class, class... Args>
struct callable: std::false_type {
};

template <class T, class... Args>
struct callable<T, void_t<std::result_of_t<T(Args...)>>, Args...>:std::true_type {
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, void, Args...>::value;

[live demo]

Cameleer answered 29/11, 2016 at 15:45 Comment(8)
I like this. I did try to do similar but failed. Great example (I've upvoted you) :-) In your example I could use void_t rather than your decltype trick, but it's useful to have learned that decltype trick - thanks!Ease
@Ease I prefer decltype over void_t because it allow you to do custom adjustment... You could for example change any type to int if you prefer - you're not doomed to change everything to void... What is more you don't need to implement anything as you get decltype in c++11 out of the boxCameleer
Are there any cases where this won't work? It's remarkably simple and elegant. There is a similar version here: talesofcpp.fusionfenix.com/post-11/true-story-call-me-maybeEase
@Ease Well there is a case I can think of - if there is no default constructor of the function result type but it can be easily repaired for this use case by using decltype(std::declval<std::result_of_t<T(Args...)>*>(), void()) instead of previous decltype statement or you could go along with the void_t as you suggested. Is there any other cases - maybe but I can't think of one right now...Cameleer
...that's not how you "repair" it. What if result_of_t returns a reference type?Nickola
@Nickola Good point but it definitly should be possible to repair it by some tag class... Unfortunetly I don't have computer right now to present ready approach...Cameleer
@Nickola I edited the answer. Can you see maybe some other drawbacks of the solution that I couldn't notice?Cameleer
/sigh. std::declval<std::result_of_t<...>>() is enough for the non-constructibility issue, but regardless void_t is superior. There are annoying subtleties with , void() - that still does overload resolution and may trigger undesirable template instantiations.Nickola
A
2

Here's how I'd approach this:

namespace detail {

template<typename Func, typename...Params> static auto helper(int) ->
    decltype((void)std::declval<Func>()(std::declval<Params>()...), std::true_type{});

template<typename Func, typename...Params> static std::false_type helper(...);

}

template<typename Func, typename... Params> struct callable:
    decltype(detail::helper<Func, Params...>(0)){};

template <class F, class... Args> constexpr auto callable_v =
    callable<F, Args...>::value;

demo

It's a poor man's version of C++1z's is_callable, but it doesn't handle pointers to members. Other than that, I think it's fine.

Aftertime answered 29/11, 2016 at 14:7 Comment(1)
thanks, I've given you an upvote as it's very interesting and much simpler than my code. It doesn't help me understand how I could fix my code though :-)Ease
C
2

The shorten use of std::result_of to do what you want could look as follows:

template <class T, class, class... Args>
struct callable: std::false_type {
};

template <class T, class... Args>
struct callable<T, decltype(std::result_of_t<T(Args...)>(), void()), Args...>:std::true_type {
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, void, Args...>::value;

[live demo]

you need to remember that type returned by result_of is always the result type of a function you pass to this trait by type. To let your sfinae work you need a method to change this type to void in every possible situation. You can accomplish it by using the trick with decltype (decltype(std::result_of_t<T(Args...)>(), void())).

Edit:

To elaborate the thread from comments about the possible drawbacks of the solution. The std::result_of_t<T(Args...)> type don't need to be equipped with a default non-parametric constructor and as such the sfinae may cause false negative result of callable_v for function that result in this kind of types. In comments I proposed a workaround for the issue that does not really solve the problem or actually generate a new one:

decltype(std::declval<std::result_of_t<T(Args...)>*>(), void())

Intention of this code was to make sfinae work as in previously proposed solution but in case of non-constructable types to create an easy to construct (I thought) object of pointer to given type... In this reasoning I didn't take into consideration the types that one cannot create a pointer to e.g. references. This one again can be workaround by using some additional wrapper class:

decltype(std::declval<std::tuple<std::result_of_t<T(Args...)>>*>(), void())

or by decaying the result type:

decltype(std::declval<std::decay_t<std::result_of_t<T(Args...)>>*>(), void())

but I think it might not be worth it and maybe use of void_t is actually a more straightforward solution:

template <class...>
struct voider {
    using type = void;
};

template <class... Args>
using void_t = typename voider<Args...>::type;

template <class T, class, class... Args>
struct callable: std::false_type {
};

template <class T, class... Args>
struct callable<T, void_t<std::result_of_t<T(Args...)>>, Args...>:std::true_type {
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, void, Args...>::value;

[live demo]

Cameleer answered 29/11, 2016 at 15:45 Comment(8)
I like this. I did try to do similar but failed. Great example (I've upvoted you) :-) In your example I could use void_t rather than your decltype trick, but it's useful to have learned that decltype trick - thanks!Ease
@Ease I prefer decltype over void_t because it allow you to do custom adjustment... You could for example change any type to int if you prefer - you're not doomed to change everything to void... What is more you don't need to implement anything as you get decltype in c++11 out of the boxCameleer
Are there any cases where this won't work? It's remarkably simple and elegant. There is a similar version here: talesofcpp.fusionfenix.com/post-11/true-story-call-me-maybeEase
@Ease Well there is a case I can think of - if there is no default constructor of the function result type but it can be easily repaired for this use case by using decltype(std::declval<std::result_of_t<T(Args...)>*>(), void()) instead of previous decltype statement or you could go along with the void_t as you suggested. Is there any other cases - maybe but I can't think of one right now...Cameleer
...that's not how you "repair" it. What if result_of_t returns a reference type?Nickola
@Nickola Good point but it definitly should be possible to repair it by some tag class... Unfortunetly I don't have computer right now to present ready approach...Cameleer
@Nickola I edited the answer. Can you see maybe some other drawbacks of the solution that I couldn't notice?Cameleer
/sigh. std::declval<std::result_of_t<...>>() is enough for the non-constructibility issue, but regardless void_t is superior. There are annoying subtleties with , void() - that still does overload resolution and may trigger undesirable template instantiations.Nickola
S
2

The problem with your original code is that you're using the parameter pack in a non deducible context

namespace impl
{

  template <class F, class... Args>
  struct callable : std::false_type
  {
  };

  template <class F, class... Args>
  struct callable<F, std::result_of_t<F(Args...)>> : std::true_type
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  {
  };

}

At this point in the source code parsing there might be no way that std::result_of_t<Func, int> can yield another return value, but there might be another specialization later in the file as in the following (very perverted) snippet

namespace std {

    template <>
    struct result_of<Func(int)> {
        using type = double;
    };
}

therefore your compiler should check all of them at the same time before being able to pick up the correct one.

That is also the reason why workarounds like

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

namespace impl
{

  template <typename...>
  struct callable_args
  {
  };

  template <class F, class Args, class = void>
  struct callable : std::false_type
  {
  };

  template <class F, class... Args>
  struct callable<F, callable_args<Args...>, void_t<std::result_of_t<F(Args...)>>> : std::true_type
  {
  };

}

work in your case: they help the compiler solve the dependent type as something that always resolves to void. Remember that the code above is a workaround and you should rather use is_callable (C++17) or study how is_callable is implemented and get an insight into its technical challenges.

Suffuse answered 29/11, 2016 at 17:23 Comment(2)
Wait, are you sure non-deduced context is underlying cause here and not fail in template specialization pattern matching? I'm not convinced question can be read as you suggest...Cameleer
Thanks for the info, I've given you an upvote by way of thanks. I will hunt around for compiler vendor's implementation of is_callable to understand how it works.Ease

© 2022 - 2024 — McMap. All rights reserved.