为什么我们可以使用 SFINAE 检测 operator() 的默认参数值的存在,但不能检测自由函数和 PMF 的默认参数值?
Why can we detect the presence of default parameter values of operator() with SFINAE, but not those of free functions and PMFs?
在下面的程序中,案例 1 尝试通过指向成员函数的指针使用默认参数。案例 2 尝试通过函数引用使用默认参数。案例 3 使用 operator()
中的默认参数。这里唯一有趣的断言是使用别名 can_call_with_one
的断言 - 其他断言的存在是为了证明设置的正确性。
在我可用的最新版本的 GCC、Clang 和 MSVC 中,此程序在情况 1 和情况 2 中未能通过单参数断言。
我的问题是双重的:
- 这些结果是否符合 ISO C++ 标准?
- 如果是这样,为什么案例 3 没有失败?
#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; }
函数的类型特征没有附带默认参数的信息。如果这样,您将无法分配具有默认值的函数指针,例如:
void foo(int, int = 0) {...}
至:
void(*fp)(int, int);
fp = &foo;
现在的问题是语言是否允许这样做——给定参数的默认值是否也能识别函数的类型?这意味着参数的默认值应该是 constexpr
,因此会限制默认值的可用性。这种方式例如 const char *
类型的参数不能内联定义默认值...
另一方面,如果函数的类型只包含给定参数具有默认值的信息,而不知道值本身 - 编译器将无法重建函数的默认值从函数调用时的指针。
在下面的程序中,案例 1 尝试通过指向成员函数的指针使用默认参数。案例 2 尝试通过函数引用使用默认参数。案例 3 使用 operator()
中的默认参数。这里唯一有趣的断言是使用别名 can_call_with_one
的断言 - 其他断言的存在是为了证明设置的正确性。
在我可用的最新版本的 GCC、Clang 和 MSVC 中,此程序在情况 1 和情况 2 中未能通过单参数断言。
我的问题是双重的:
- 这些结果是否符合 ISO C++ 标准?
- 如果是这样,为什么案例 3 没有失败?
#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; }
函数的类型特征没有附带默认参数的信息。如果这样,您将无法分配具有默认值的函数指针,例如:
void foo(int, int = 0) {...}
至:
void(*fp)(int, int);
fp = &foo;
现在的问题是语言是否允许这样做——给定参数的默认值是否也能识别函数的类型?这意味着参数的默认值应该是 constexpr
,因此会限制默认值的可用性。这种方式例如 const char *
类型的参数不能内联定义默认值...
另一方面,如果函数的类型只包含给定参数具有默认值的信息,而不知道值本身 - 编译器将无法重建函数的默认值从函数调用时的指针。