2

What is the simplest way to tell at compile time if a lambda (or a function object) has a default argument or not? Example:

auto f = [](int i=0){};
auto g = [](int i){};
static_assert(has_default_arg<decltype(f)>::value==true);
static_assert(has_default_arg<decltype(g)>::value==false);
Paolo Crosetto
  • 1,038
  • 7
  • 17
  • related/dupe: https://stackoverflow.com/questions/42920457/is-it-possible-to-detect-the-default-parameters-of-a-function-at-compile-time – NathanOliver Jun 11 '18 at 12:41

2 Answers2

5

I do not believe it is possible to detect this without some sort of function static reflection. You could however check if the lambda is invocable with both zero and one arguments. Example using the detection idiom:

template <class T>
using invocable_zero = decltype(std::declval<T&>()());

template <class T, class X>
using invocable_one = decltype(std::declval<T&>()(std::declval<X>()));

template <class T, class X>
struct has_default_arg : std::conjunction< 
    std::experimental::is_detected<invocable_zero, T>,
    std::experimental::is_detected<invocable_one, T, X>
> { };

live example on wandbox.org

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
1

As NathanOliver pointed out in the comment, you cannot do this for normal function object. So we only focus on lambda.

First, we can make a helper class that checks whether F can be invoked with the arguments chosen from Args... via an index sequence Index_sequence:

template <typename F, typename Index_sequence, typename... Args>
struct is_invocable_for_indices : std::false_type {};

template <typename F, size_t... Is, typename... Args>
struct is_invocable_for_indices<F, std::index_sequence<Is...>, Args...> 
    : std::is_invocable<F, std::tuple_element_t<Is, std::tuple<Args...>>...> {};

template <typename F, typename Index_sequence, typename... Args>
inline constexpr bool is_invocable_for_indices_v = is_invocable_for_indices<F, Index_sequence, Args...>::value;

// example use
auto f = [](int i = 0) {};
auto g = [](int i) {};
static_assert(is_invocable_for_indices_v<decltype(f), std::index_sequence<>, int>);
static_assert(!is_invocable_for_indices_v<decltype(g), std::index_sequence<>, int>);
static_assert(is_invocable_for_indices_v<decltype(g), std::index_sequence<0>, int>);

Let Args be the parameter types of F, which can be detected via decltype(&F::operator()) (the idea comes from this answer). Now you can check if F has default argument by checking if F can be invoked with the first sizeof...(Args) - 1 arguments of Args. So we can define has_defulat_arg as follows:

template <typename F, typename OperatorType>
struct has_defulat_arg_impl : std::false_type {};

template <typename F, typename R, typename... Args>
struct has_defulat_arg_impl<F, R(F::*)(Args...) const>    
    : is_invocable_for_indices<F, std::make_index_sequence<sizeof...(Args) - 1>, Args...> {};

// specialization for the case where sizeof...(Args) == 0
template <typename F, typename R>
struct has_defulat_arg_impl<F, R(F::*)() const> : std::false_type {};

template <typename F>
using has_defulat_arg = has_defulat_arg_impl<F, decltype(&F::operator())>;

template <typename F>
inline constexpr bool has_defulat_arg_v = has_defulat_arg<F>::value;

// example use
auto f = [](int i = 0) {};
auto g = [](int i) {};
static_assert(has_defulat_arg_v<decltype(f)>);
static_assert(!has_defulat_arg_v<decltype(g)>);

LIVE EXAMPLE

xskxzr
  • 12,442
  • 12
  • 37
  • 77