Boost logo

Boost :

Subject: [boost] [TypeTraits] propose new callable-type call traits
From: Hui Li (hui.li_at_[hidden])
Date: 2014-01-11 14:05:56


I've implemented a few traits classes (code attached below) that, for a given callable-type T, and a set of Args..., detects whether the function call T::operator()(Args...) would be
(1) valid,
(2) ambiguous, or
(3) none is viable.
where T is allowed to have arbitrary overloaded operator()'s.

This could be useful for programmers who want to
(1) make the function call only when it's valid,
(2) generate customized error/warning messages when it's ambiguous, or handle/redirect it without getting an ambiguous compiler error, and
(3) redirect non-viable calls to some default behavior, which can be different than how it's handled when the call is ambiguous.

If people are interested, I suppose this could be a useful addition to TypeTraits.
I would appreciate any feedback!

For clarity, the implementation and demonstration attached below is written in c++11. It can be easily ported to c++98/03 compilers by using TypeTratis to replace the std type traits, BOOST_TYPEOF_KEYWORD to replace decltype, and Boost.Preprocessor to generate code for arbitrary arity.

- Hui Li

#include <utility>
#include <type_traits>

template < typename T > struct is_callable;
template < typename Signature > struct has_valid_call;
template < typename Signature > struct has_ambiguous_call;
template < typename Signature > struct has_no_viable_call;

template < typename T >
struct is_callable
{
    struct yes {};
    struct no {};

    struct ambiguate_seed { void operator()(); };
    
    template < typename U, bool=std::is_class<U>::value >
    struct ambiguate : U, ambiguate_seed {};
    
    template < typename U >
    struct ambiguate<U,false> : ambiguate_seed {};

    template < typename U, typename = decltype(&U::operator()) > static constexpr no test(int);
    template < typename > static constexpr yes test(...);

    static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;
    typedef std::integral_constant<bool,value> type;
};

template < typename T, typename... Args >
struct has_valid_call< T(Args...) >
{
   struct yes {};
   struct no {};

   template < typename U, bool = is_callable<U>::value >
   struct impl
   {
      template < typename V, typename = typename std::result_of<V(Args...)>::type >
      struct test_result { using type = yes; };

      template < typename V > static constexpr typename test_result<V>::type test(int);
      template < typename > static constexpr no test(...);

      static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;
      using type = std::integral_constant<bool, value>;
   };

   template < typename U >
   struct impl<U,false> : std::false_type {};

   static constexpr bool value = impl<T>::value;
   using type = std::integral_constant<bool, value>;
};

template < typename T, typename... Args >
struct has_ambiguous_call< T(Args...) >
{
   struct ambiguate_seed { void operator()(...); };

   template < class U, bool = is_callable<U>::value >
   struct ambiguate : U, ambiguate_seed
   {
     using ambiguate_seed::operator();
     using U::operator();
   };

   template < class U >
   struct ambiguate<U,false> : ambiguate_seed {};

   static constexpr bool value = not has_valid_call< ambiguate<T>(Args...) >::value;
   using type = std::integral_constant<bool, value>;
};

template < typename T, typename... Args >
struct has_no_viable_call< T(Args...) >
 : std::integral_constant<bool,
   not (has_valid_call<T(Args...)>::value or has_ambiguous_call<T(Args...)>::value)>
{};

#include <string>
#include <vector>

struct test
{
   void operator()(int);
   void operator()(double);
   void operator()(int,double);
   
   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   operator()(const T&, int=0){}
   
   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   operator()(const std::vector<T>&, T*){}
   
   template < typename T >
   int operator()(const std::string&, int){}
};

int main(int argc, const char * argv[])
{

   static_assert( is_callable<test>::value , "");
   static_assert( not is_callable<int>::value , "");

   static_assert( has_valid_call<test(char const*,long)>::value , "");
   static_assert( has_valid_call<test(std::string&,long)>::value , "");
   
   static_assert( has_valid_call<test(std::vector<int>, int*)>::value , "");

   static_assert( has_no_viable_call<test(std::vector<double>, double*)>::value , "");
   
   static_assert( has_valid_call<test(int)>::value , "");

   static_assert( has_valid_call<test(int,double)>::value , "");
   static_assert( not has_valid_call<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_call<test(double)>::value , "");
   static_assert( has_ambiguous_call<test(unsigned)>::value , "");
   static_assert( not has_no_viable_call<test(unsigned)>::value , "");
   
   static_assert( not has_no_viable_call<test(int)>::value , "");

   static_assert( has_no_viable_call<test(void)>::value , "");
   
   return 0;

}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk