代码之家  ›  专栏  ›  技术社区  ›  Max Langhof

带默认参数的函数上的SFINAE-自由函数vs运算符()

  •  19
  • Max Langhof  · 技术社区  · 6 年前

    this answer 研究它如何处理带有默认参数的函数。令我惊讶的是,对于自由函数和 operator()

    template <typename F>
    auto func(F f) -> decltype(f(42))
    {
        int a = 51;
        return f(51);
    }
    
    template <typename F>
    auto func(F f) -> decltype(f(42, 42))
    {
        int a = 0;
        int b = 10;
        return f(a, b);
    }
    
    int defaultFree(int a, int b = 0)
    {
        return a;
    }
    
    auto defaultLambda = [](int a, int b = 0)
    {
        return a;
    };
    
    int foo()
    {
        return func(defaultFree);  
        //return func(defaultLambda);
    }
    

    Godbolt link

    这个 func(defaultFree) 以上版本编译时 func 模板可用。正如预期的那样,它选择第二个参数,因为默认参数不被认为是函数签名的一部分。事实上,移除第二个 功能 模板导致编译错误。

    然而, func(defaultLambda) not compile 模板匹配。删除任何一个都会使此版本编译。

    (如果手动编写 struct 有类似的 运算符() ,当然。最新的 gcc clang MSVC 大家也都同意。)

    在未赋值的SFINAE上下文中考虑默认参数的原因是什么 但不是为了自由功能?

    2 回复  |  直到 6 年前
        1
  •  9
  •   Yakk - Adam Nevraumont    6 年前

    函数名不是C++对象的名称。

    相反,当您使用函数名时,会发生一系列转换。重载解析基于调用或(隐式或显式)转换上下文完成,并生成指针。

    函数的默认参数是重载解析的一部分。它们从不作为函数指针类型的一部分传递。

    您可以创建一个简单的包装器,将函数名转换为函数对象:

    #define RETURNS(...) \
      noexcept(noexcept(__VA_ARGS__)) \
      -> decltype(__VA_ARGS__) \
      { return __VA_ARGS__; }
    
    #define OVERLOADS_OF(...) \
      [](auto&&...args) \
      RETURNS( __VA_ARGS__( decltype(args)(args)... ) )
    

    通过此操作,您可以修改代码:

    return func(OVERLOADS_OF(defaultFree));
    

    func 使之产生歧义。

    现在, OVERLOADS_OF(defaultFree) defaultFree

    函数对象不是函数。lambda是函数对象,也是 OVERLOADS_OF . 函数对象可以传入并重载 operator() 考虑过;他们可以记住他们的默认参数,做SFINAE等。


    为了解决你的问题,你应该让一个过载看起来比另一个好。

    ... :

    namespace impl {
      template <typename F>
      auto func(F f,...) -> decltype(f(42))
      {
        int a = 51;
        return f(51);
      }
    
      template <typename F>
      auto func(F f, int) -> decltype(f(42, 42))
      {
        int a = 0;
        int b = 10;
        return f(a, b);
      }
    }
    template <typename F>
    auto func(F f) -> decltype( impl::func(f, 0) )
    {
      return impl::func(f, 0);
    }
    

    诀窍在于 int ... 当你经过的时候 0 .

    您还可以更明确地生成诸如“可以用1个参数调用”、“可以用2个参数调用”之类的特征,然后声明只有当可以用1个参数而不是2个参数调用时才启用1 arg大小写。

    也有基于标签调度的过载解析排序技术。

        2
  •  17
  •   StoryTeller - Unslander Monica    6 年前

    当您将free函数作为参数传递时,它将经历函数到指针的转换。当这种情况发生时,默认参数( which is not a part of the function's type

    lambda的类型不会经历这样的调整。未赋值表达式必须包含 operator()

    当无捕获lambda被迫转换为函数指针时(例如 func(+defaultLambda); ,由@YSC提供),歧义消失了,原因也是一样的。