为什么我们可以使用SFINAE检测operator()的默认参数值,而不是自由函数和PMF的默认参数值?

时间:2016-03-13 04:44:37

标签: c++ templates c++11 sfinae

在下面的程序中,case 1尝试通过指向成员函数的指针使用默认参数。情况2尝试通过函数引用使用默认参数。案例3使用operator()中的默认参数。这里唯一有趣的断言是使用别名can_call_with_one的那些 - 其他存在是为了证明设置的正确性。

在我可以使用的最新版本的GCC,Clang和MSVC中,该程序在案例1和2中的单参数断言失败。

我的问题有两个:

  1. 这些结果是否与ISO C ++标准一致?
  2. 如果是这样,为什么案例3不会失败?
  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; }
    

    runnable version

1 个答案:

答案 0 :(得分:0)

该功能的类型特征没有附带默认参数的信息。如果它你将无法分配具有默认值的函数的指针,如:

void foo(int, int = 0) {...}

为:

void(*fp)(int, int);
fp = &foo;

现在的问题是语言是否允许 - 如果给定参数的默认值也能识别函数的类型吗?这意味着参数的默认值应为constexpr,因此会限制默认值的可用性。这种方式,例如类型const char *的参数不能具有内联定义的默认值...

另一方面,如果函数的类型只携带给定参数具有默认值的信息而不知道值本身 - 编译器将无法重建函数的默认值从函数调用时的指针。