Search code examples
c++c++14boost-hana

How to check if method exists when arguments are given by the tuple?


I would like to check whether a function with the given signature exists. The twist is that the signature is given by the tuple:

#include <boost/hana/type.hpp>
#include <boost/hana/tuple.hpp>
#include <tuple>
namespace hana = boost::hana;

struct LibA {
   static int foo(int, float, double) { return 1;}
};

struct LibB {
   static int bar(float, float, float) { return 2;}
};

template<typename Lib, typename... Args>
int call(Lib, hana::tuple<Args...> args) {
   auto hasFoo = hana::is_valid(hana::unpack)(args, Lib::foo);
   auto hasBar = hana::is_valid(hana::unpack)(args, Lib::bar);

   static_assert(hana::if_(hasFoo || hasBar, true, false), "Cannot find both positional and named version of the functor.");
   return(hana::if_(hasFoo, 
          hana::unpack(args, Lib::foo),
          hana::unpack(args, Lib::bar)
   ));
}


int main() {
   int i; float f; double d;
   call(LibA(), hana::make_tuple(i, f, d)); //compile error: 'bar' is not a member of LibA
   call(LibB(), hana::make_tuple(f, f, f)); //compile error: 'foo' is not a member of libB
}

The hana::is_valid returns a compile error rather than handle it as it is supposed to. I understand it is caused by the indirection - it only validates the call to the hana::unpack, which is ok, but not the nested call to the Lib::foo. Is there a walkaround for this problem?


Solution

  • When you refer inside the function template definition to Lib::x that is not an immediate template expansion context, so SFINAE doesn't apply there and hence you get a compiler error.

    Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

    An alternative working solution:

    struct LibA {
        static int foo(int, float, double) { return 1; }
    };
    
    struct LibB {
        static int bar(float, float, float) { return 2; }
    };
    
    template<typename Lib, typename... Args>
    auto call(Lib, hana::tuple<Args...> args) -> decltype(hana::unpack(args, Lib::foo)) {
        return hana::unpack(args, Lib::foo);
    }
    
    template<typename Lib, typename... Args>
    auto call(Lib, hana::tuple<Args...> args) -> decltype(hana::unpack(args, Lib::bar)) {
        return hana::unpack(args, Lib::bar);
    }
    
    int main() {
        int i; float f; double d;
        call(LibA(), hana::make_tuple(i, f, d));
        call(LibB(), hana::make_tuple(f, f, f));
    }
    

    In the above, that trailing return type with decltype is an immediate template expansion context and SFINAE works as expected there.