Search code examples
c++14boost-hana

How can I use Boost.Hana to determine whether a functor has a call operator that can be invoked with a particular template argument?


In my application, I want to determine at compile time whether an arbitrary functor type Func has a nullary call operator that can be invoked with a given explicit template argument T. Based on a previous SO answer that I found, I came up with the following:

#include <boost/hana.hpp>
#include <iostream>
#include <type_traits>

namespace hana = boost::hana;

namespace detail 
{
    template <typename T>
    auto can_call = hana::is_valid([](auto &&f) -> 
        decltype(f.template operator()<T>()) { });
}

template <typename Func, typename T>
constexpr auto can_call() ->
    decltype(detail::can_call<typename std::remove_reference<T>::type>(
        std::declval<Func>())) { return {}; }

struct foo
{
    template <typename T, typename = 
        std::enable_if_t<!std::is_same<T, char>::value>>
    void operator()() const { }
};

int main()
{
    std::cout << "char: " << can_call<foo, char>() << std::endl;
    std::cout << "int: " << can_call<foo, int>() << std::endl;
}

I would expect this example to print out:

char: 0
int: 1

Since the char template argument type is explicitly enable_if-ed out in foo. I've tried the following compilers:

  • Apple clang v8.0.0: The example compiles and runs as expected.
  • mainline clang v3.9.1+ (via Wandbox): The example compiles and runs as expected.
  • mainline clang v3.6.0 - v3.8.1 (via Wandbox): The compiler dies with an internal error.
  • g++ 7.0 trunk, 20170410 (via Wandbox): The compilation fails with the following errors:

    dd.cc: In instantiation of ‘auto detail::can_call<char>’:
    dd.cc:15:14:   required by substitution of ‘template<class Func, class T> constexpr decltype (can_call<typename std::remove_reference<_To>::type>(declval<Func>())) can_call() [with Func = foo; T = char]’
    dd.cc:25:50:   required from here
    dd.cc:10:10: error: ‘auto detail::can_call<char>’ has incomplete type
        auto can_call = hana::is_valid([](auto &&f) -> decltype(f.template operator()<T>()) { });
            ^~~~~~~~
    dd.cc: In function ‘int main()’:
    dd.cc:25:50: error: no matching function for call to ‘can_call<foo, char>()’
        std::cout << "char: " << can_call<foo, char>() << std::endl;
                                                    ^
    dd.cc:14:16: note: candidate: template<class Func, class T> constexpr decltype (can_call<typename std::remove_reference<_To>::type>(declval<Func>())) can_call()
    constexpr auto can_call() ->
                ^~~~~~~~
    dd.cc:14:16: note:   substitution of deduced template arguments resulted in errors seen above
    dd.cc: In instantiation of ‘auto detail::can_call<int>’:
    dd.cc:15:14:   required by substitution of ‘template<class Func, class T> constexpr decltype (can_call<typename std::remove_reference<_To>::type>(declval<Func>())) can_call() [with Func = foo; T = int]’
    dd.cc:26:48:   required from here
    dd.cc:10:10: error: ‘auto detail::can_call<int>’ has incomplete type
        auto can_call = hana::is_valid([](auto &&f) -> decltype(f.template operator()<T>()) { });
            ^~~~~~~~
    dd.cc:26:48: error: no matching function for call to ‘can_call<foo, int>()’
        std::cout << "int: " << can_call<foo, int>() << std::endl;
                                                ^
    dd.cc:14:16: note: candidate: template<class Func, class T> constexpr decltype (can_call<typename std::remove_reference<_To>::type>(declval<Func>())) can_call()
    constexpr auto can_call() ->
                ^~~~~~~~
    dd.cc:14:16: note:   substitution of deduced template arguments resulted in errors seen above
    

It seems to not like my use of hana::is_valid() to determine whether the specified operator exists. However, I think the way I'm using it is consistent with its intended use.

Is this a bug in gcc, a more lenient implementation in contemporary clang versions, or did I implement this type of check incorrectly? It seems like this is definitely within Hana's wheelhouse; I'm just trying to wrap my head around its new model of constexpr metaprogramming.


Solution

  • Here is a workaround that uses a struct "functor" instead of a lambda and an extra layer of indirection for the type of the is_valid instance to appease gcc.

    namespace detail 
    {
        template <typename T>
        struct check_can_call { 
          template <typename F>
          constexpr auto operator()(F&& f) -> 
            decltype(f.template operator()<T>()) { }
        };
    
        template <typename T>
        using is_call_valid = decltype(hana::is_valid(check_can_call<T>{}));
    
        template <typename T>
        constexpr is_call_valid<T> can_call{};
    }