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

////////////////////////////////////////////////////////////////////////////////
// Detection idiom, from C++ Library fundamentals extension in N4436

namespace std { 
namespace experimental {

// A void_t implementation that works with gcc-4.9 (workaround for bug 64395
// From: http://stackoverflow.com/questions/35753920/why-does-the-void-t-detection-idiom-not-work-with-gcc-4-9
namespace _void_t_impl {

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

} // end namepace _void_t_impl

template <class... T>
using void_t = typename _void_t_impl::make_void<T...>::type;

template <
  class Default,
  class _always_void,
  template <class...> class Op,
  class... Args
>
struct detector {
  constexpr static auto value = false;
  using type = Default;
};

// specialization recognizes and handles only types supporting Op
template <
  class Default,
  template <class...> class Op,
  class... Args
>
struct detector<Default, void_t<Op<Args...>>, Op, Args...> {
  constexpr static auto value = true;
  using type = Op<Args...>;
};

struct nonesuch {
  nonesuch() = delete;
  ~nonesuch() = delete;
  nonesuch(nonesuch const&) = delete;
  void operator=(nonesuch const&) = delete;
  constexpr static auto value = false;
};

template <template <class...> class Op, class... Args>
using is_detected = detector<nonesuch, void, Op, Args...>;

template <template <class...> class Op, class... Args>
using detected_t = typename is_detected<Op, Args...>::type;

template <class Default, template <class...> class Op, class... Args>
using detected_or = detector<Default, void, Op, Args...>;

template <class Default, template <class...> class Op, class... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;

template <class Expected, template<class...> class Op, class... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;

template <class To, template <class...> class Op, class... Args>
using is_detected_convertible = std::is_convertible<detected_t<Op, Args...>, To>;

} // end namespace experimental
} // end namespace std

////////////////////////////////////////////////////////////////////////////////

using namespace std;
using namespace std::experimental;

static constexpr auto max_num_args = 127;

struct any {
  template <typename T,
    typename = enable_if_t<
      not is_same<T, remove_reference_t<T>>::value
    >
  >
  operator T();
  template <typename T,
    typename = enable_if_t<
      is_same<T, remove_reference_t<T>>::value
    >
  >
  operator T&();
  template <typename T,
    typename = enable_if_t<
      is_same<T, remove_reference_t<T>>::value
    >
  >
  operator T&&();
};


template <typename F, typename... Args>
using callable_archetype = decltype( declval<F>()(declval<Args>()...) );
template <typename F, typename... Args>
using is_callable_with_args = is_detected<callable_archetype, F, Args...>;

template <typename F, size_t I = 0,  typename... Args>
struct count_args
  : conditional<
      is_callable_with_args<F, Args...>::value,
      integral_constant<size_t, I>,
      count_args<F, I+1, Args..., any>
    >::type::type
{ };

template <typename F, typename... Args>
struct count_args<F, max_num_args, Args...> : integral_constant<size_t, max_num_args> { };

// Some example cases

void foo(int i, int j) { }
struct bar {
  void operator()(std::string const&, std::ostream const&, int) const { }
};
void baz(std::string&, const std::string&) { }

int main(int argc, char** argv) {
  cout << count_args<decltype(foo)>::value << endl; // Should be 2 
  cout << count_args<bar>::value << endl; // should be 3
  cout << count_args<decltype(baz)>::value << endl; // fails because of lvalue reference!
}

