In the program below, case 1 attempts to use a default parameter via pointer-to-member-function. Case 2 attempts to use a default parameter via function reference. Case 3 uses the default parameter in operator()
. The only interesting assertions here are the ones using the alias can_call_with_one
- the others exist to prove correctness of the setup.
In the latest versions of GCC, Clang, and MSVC that are available to me, this program fails the single-argument assertions in cases 1 and 2.
My question is twofold:
- Are these results consistent with the ISO C++ standard?
- If so, why does case 3 not fail?
#include <type_traits>
#include <utility>
struct substitution_failure {};
substitution_failure check(...);
template<typename Pmf, typename T, typename... Args>
auto check(Pmf pmf, T t, Args&&... args) ->
decltype((t.*pmf)(std::forward<Args>(args)...))*;
template<typename Fn, typename... Args>
auto check(Fn&& f, Args&&... args) ->
decltype(f(std::forward<Args>(args)...))*;
template<typename T>
using test_result = std::integral_constant<bool,
!std::is_same<T, substitution_failure>::value
>;
template<typename... Ts>
auto can_invoke(Ts&&... ts) ->
test_result<decltype(check(std::forward<Ts>(ts)...))>;
namespace case_1 {
//pointer to member function
struct foo {
int bar(int, int = 0);
};
using can_call_with_one = decltype(can_invoke(&foo::bar, foo{}, 0));
using can_call_with_two = decltype(can_invoke(&foo::bar, foo{}, 0, 0));
using can_call_with_three = decltype(can_invoke(&foo::bar, foo{}, 0, 0, 0));
static_assert(can_call_with_one{}, "case 1 - can't call with one argument");
static_assert(can_call_with_two{}, "case 1 - can't call with twp arguments");
static_assert(!can_call_with_three{}, "case 1 - can call with three arguments");
}
namespace case_2 {
//function reference
int foo(int, int = 0);
using can_call_with_one = decltype(can_invoke(foo, 0));
using can_call_with_two = decltype(can_invoke(foo, 0, 0));
using can_call_with_three = decltype(can_invoke(foo, 0, 0, 0));
static_assert(can_call_with_one{}, "case 2 - can't call with one argument");
static_assert(can_call_with_two{}, "case 2 - can't call with two arguments");
static_assert(!can_call_with_three{}, "case 2 - can call with three arguments");
}
namespace case_3 {
//function object
struct foo {
int operator()(int, int = 0);
};
using can_call_with_one = decltype(can_invoke(foo{}, 0));
using can_call_with_two = decltype(can_invoke(foo{}, 0, 0));
using can_call_with_three = decltype(can_invoke(foo{}, 0, 0, 0));
static_assert(can_call_with_one{}, "case 3 - can't call with one argument");
static_assert(can_call_with_two{}, "case 3 - can't call with two arguments");
static_assert(!can_call_with_three{}, "case 3 - can call with three arguments");
}
int main() { return 0; }