SFINAE on functions with default parameters - free function vs operator()

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
7
down vote

favorite
1












I was playing around with this answer to investigate how it handles functions with default parameters. To my surprise, the results are different for free functions and 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



The func(defaultFree) version above compiles while both func templates are available. As expected, it picks the second one because default parameters are not considered part of the function signature. Indeed, removing the second func template causes a compilation error.



However, func(defaultLambda) does not compile due to ambiguity: Both func templates match. Removing either one makes this version compile.



(The same happens if you manually write a struct with a similar operator(), of course. Latest gcc, clang and MSVC all agree on it, too.)



What is the reason that the default parameter is considered inside the unevaluated SFINAE context for operator() but not for the free function?










share|improve this question



























    up vote
    7
    down vote

    favorite
    1












    I was playing around with this answer to investigate how it handles functions with default parameters. To my surprise, the results are different for free functions and 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



    The func(defaultFree) version above compiles while both func templates are available. As expected, it picks the second one because default parameters are not considered part of the function signature. Indeed, removing the second func template causes a compilation error.



    However, func(defaultLambda) does not compile due to ambiguity: Both func templates match. Removing either one makes this version compile.



    (The same happens if you manually write a struct with a similar operator(), of course. Latest gcc, clang and MSVC all agree on it, too.)



    What is the reason that the default parameter is considered inside the unevaluated SFINAE context for operator() but not for the free function?










    share|improve this question

























      up vote
      7
      down vote

      favorite
      1









      up vote
      7
      down vote

      favorite
      1






      1





      I was playing around with this answer to investigate how it handles functions with default parameters. To my surprise, the results are different for free functions and 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



      The func(defaultFree) version above compiles while both func templates are available. As expected, it picks the second one because default parameters are not considered part of the function signature. Indeed, removing the second func template causes a compilation error.



      However, func(defaultLambda) does not compile due to ambiguity: Both func templates match. Removing either one makes this version compile.



      (The same happens if you manually write a struct with a similar operator(), of course. Latest gcc, clang and MSVC all agree on it, too.)



      What is the reason that the default parameter is considered inside the unevaluated SFINAE context for operator() but not for the free function?










      share|improve this question















      I was playing around with this answer to investigate how it handles functions with default parameters. To my surprise, the results are different for free functions and 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



      The func(defaultFree) version above compiles while both func templates are available. As expected, it picks the second one because default parameters are not considered part of the function signature. Indeed, removing the second func template causes a compilation error.



      However, func(defaultLambda) does not compile due to ambiguity: Both func templates match. Removing either one makes this version compile.



      (The same happens if you manually write a struct with a similar operator(), of course. Latest gcc, clang and MSVC all agree on it, too.)



      What is the reason that the default parameter is considered inside the unevaluated SFINAE context for operator() but not for the free function?







      c++ c++11 language-lawyer sfinae






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited 2 hours ago









      Barry

      168k18285523




      168k18285523










      asked 2 hours ago









      Max Langhof

      5,018927




      5,018927






















          2 Answers
          2






          active

          oldest

          votes

















          up vote
          8
          down vote













          When you pass the free function as an argument, it undergoes the function-to-pointer conversion. When that happens, the default argument (which is not a part of the function's type) is gone. It's now a pointer to a function taking two parameters, and as such only one SFINAE check can pass for it.



          The lambda's type doesn't undergo such an adjustment. The unevaluated expression must involve overload resolution for operator(), and that finds the declaration with the default argument, which allows it be used in a call with a single argument.



          When the capturesless lambda is forced to undergo a conversion to a function pointer (for instance func(+defaultLambda);, courtesy of @YSC), the ambiguity is gone, for the same reason.






          share|improve this answer





























            up vote
            3
            down vote













            A function name is not the name of a C++ object.



            Instead, when you use a function name, a bunch of conversions occur. Overload resolution is done based on the calling or (implicit or explicit) casting context, and a pointer is produced.



            Default arguments on a function are part of overload resolution. They are never passed as part of a function pointer type.



            You can create a simple wrapper that turns a function name into a function object:



            #define RETURNS(...) 
            noexcept(noexcept(__VA_ARGS__))
            -> decltype(__VA_ARGS__)
            return __VA_ARGS__;

            #define OVERLOADS_OF(...)
            (auto&&...args)
            RETURNS( __VA_ARGS__( decltype(args)(args)... ) )


            with this, you can modify your code:



            return func(OVERLOADS_OF(defaultFree));


            and get the default arguments to be considered by func and SFINAE to result in ambiguity.



            Now, OVERLOADS_OF(defaultFree) is a function object which SFINAE tests if its arguments can be passed to a callable called defaultFree. This permits 1 argument to be passed.



            Function objects are not functions. A lambda is a function object, as is the return type of OVERLOADS_OF. Function objects can be passed in and their overloaded operator() considered; they can remember their default parameters, do SFINAE, etc.



            So when you pass the lambda, both possibilities are legal. When you pass a function, it becomes a function pointer to the no-default-argument call to the function, and that unambiguously does not accept 1 parameter.




            To fix your problem you should make one overload look better than the other.



            One approach is to use ...:



            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);



            the trick is that int is preferred over ... when you pass 0.



            You could also be more explicit, and generate traits like "can be called with 1 argument", "can be called with 2 arguments", then state that the 1 arg case is only enabled when you can be called with 1 but not 2 arguments.



            There are also tag dispatching based overload resolution ordering techniques.






            share|improve this answer




















              Your Answer





              StackExchange.ifUsing("editor", function ()
              StackExchange.using("externalEditor", function ()
              StackExchange.using("snippets", function ()
              StackExchange.snippets.init();
              );
              );
              , "code-snippets");

              StackExchange.ready(function()
              var channelOptions =
              tags: "".split(" "),
              id: "1"
              ;
              initTagRenderer("".split(" "), "".split(" "), channelOptions);

              StackExchange.using("externalEditor", function()
              // Have to fire editor after snippets, if snippets enabled
              if (StackExchange.settings.snippets.snippetsEnabled)
              StackExchange.using("snippets", function()
              createEditor();
              );

              else
              createEditor();

              );

              function createEditor()
              StackExchange.prepareEditor(
              heartbeatType: 'answer',
              convertImagesToLinks: true,
              noModals: false,
              showLowRepImageUploadWarning: true,
              reputationToPostImages: 10,
              bindNavPrevention: true,
              postfix: "",
              onDemand: true,
              discardSelector: ".discard-answer"
              ,immediatelyShowMarkdownHelp:true
              );



              );













               

              draft saved


              draft discarded


















              StackExchange.ready(
              function ()
              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52370305%2fsfinae-on-functions-with-default-parameters-free-function-vs-operator%23new-answer', 'question_page');

              );

              Post as a guest






























              2 Answers
              2






              active

              oldest

              votes








              2 Answers
              2






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes








              up vote
              8
              down vote













              When you pass the free function as an argument, it undergoes the function-to-pointer conversion. When that happens, the default argument (which is not a part of the function's type) is gone. It's now a pointer to a function taking two parameters, and as such only one SFINAE check can pass for it.



              The lambda's type doesn't undergo such an adjustment. The unevaluated expression must involve overload resolution for operator(), and that finds the declaration with the default argument, which allows it be used in a call with a single argument.



              When the capturesless lambda is forced to undergo a conversion to a function pointer (for instance func(+defaultLambda);, courtesy of @YSC), the ambiguity is gone, for the same reason.






              share|improve this answer


























                up vote
                8
                down vote













                When you pass the free function as an argument, it undergoes the function-to-pointer conversion. When that happens, the default argument (which is not a part of the function's type) is gone. It's now a pointer to a function taking two parameters, and as such only one SFINAE check can pass for it.



                The lambda's type doesn't undergo such an adjustment. The unevaluated expression must involve overload resolution for operator(), and that finds the declaration with the default argument, which allows it be used in a call with a single argument.



                When the capturesless lambda is forced to undergo a conversion to a function pointer (for instance func(+defaultLambda);, courtesy of @YSC), the ambiguity is gone, for the same reason.






                share|improve this answer
























                  up vote
                  8
                  down vote










                  up vote
                  8
                  down vote









                  When you pass the free function as an argument, it undergoes the function-to-pointer conversion. When that happens, the default argument (which is not a part of the function's type) is gone. It's now a pointer to a function taking two parameters, and as such only one SFINAE check can pass for it.



                  The lambda's type doesn't undergo such an adjustment. The unevaluated expression must involve overload resolution for operator(), and that finds the declaration with the default argument, which allows it be used in a call with a single argument.



                  When the capturesless lambda is forced to undergo a conversion to a function pointer (for instance func(+defaultLambda);, courtesy of @YSC), the ambiguity is gone, for the same reason.






                  share|improve this answer














                  When you pass the free function as an argument, it undergoes the function-to-pointer conversion. When that happens, the default argument (which is not a part of the function's type) is gone. It's now a pointer to a function taking two parameters, and as such only one SFINAE check can pass for it.



                  The lambda's type doesn't undergo such an adjustment. The unevaluated expression must involve overload resolution for operator(), and that finds the declaration with the default argument, which allows it be used in a call with a single argument.



                  When the capturesless lambda is forced to undergo a conversion to a function pointer (for instance func(+defaultLambda);, courtesy of @YSC), the ambiguity is gone, for the same reason.







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited 2 hours ago

























                  answered 2 hours ago









                  StoryTeller

                  83.1k12166230




                  83.1k12166230






















                      up vote
                      3
                      down vote













                      A function name is not the name of a C++ object.



                      Instead, when you use a function name, a bunch of conversions occur. Overload resolution is done based on the calling or (implicit or explicit) casting context, and a pointer is produced.



                      Default arguments on a function are part of overload resolution. They are never passed as part of a function pointer type.



                      You can create a simple wrapper that turns a function name into a function object:



                      #define RETURNS(...) 
                      noexcept(noexcept(__VA_ARGS__))
                      -> decltype(__VA_ARGS__)
                      return __VA_ARGS__;

                      #define OVERLOADS_OF(...)
                      (auto&&...args)
                      RETURNS( __VA_ARGS__( decltype(args)(args)... ) )


                      with this, you can modify your code:



                      return func(OVERLOADS_OF(defaultFree));


                      and get the default arguments to be considered by func and SFINAE to result in ambiguity.



                      Now, OVERLOADS_OF(defaultFree) is a function object which SFINAE tests if its arguments can be passed to a callable called defaultFree. This permits 1 argument to be passed.



                      Function objects are not functions. A lambda is a function object, as is the return type of OVERLOADS_OF. Function objects can be passed in and their overloaded operator() considered; they can remember their default parameters, do SFINAE, etc.



                      So when you pass the lambda, both possibilities are legal. When you pass a function, it becomes a function pointer to the no-default-argument call to the function, and that unambiguously does not accept 1 parameter.




                      To fix your problem you should make one overload look better than the other.



                      One approach is to use ...:



                      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);



                      the trick is that int is preferred over ... when you pass 0.



                      You could also be more explicit, and generate traits like "can be called with 1 argument", "can be called with 2 arguments", then state that the 1 arg case is only enabled when you can be called with 1 but not 2 arguments.



                      There are also tag dispatching based overload resolution ordering techniques.






                      share|improve this answer
























                        up vote
                        3
                        down vote













                        A function name is not the name of a C++ object.



                        Instead, when you use a function name, a bunch of conversions occur. Overload resolution is done based on the calling or (implicit or explicit) casting context, and a pointer is produced.



                        Default arguments on a function are part of overload resolution. They are never passed as part of a function pointer type.



                        You can create a simple wrapper that turns a function name into a function object:



                        #define RETURNS(...) 
                        noexcept(noexcept(__VA_ARGS__))
                        -> decltype(__VA_ARGS__)
                        return __VA_ARGS__;

                        #define OVERLOADS_OF(...)
                        (auto&&...args)
                        RETURNS( __VA_ARGS__( decltype(args)(args)... ) )


                        with this, you can modify your code:



                        return func(OVERLOADS_OF(defaultFree));


                        and get the default arguments to be considered by func and SFINAE to result in ambiguity.



                        Now, OVERLOADS_OF(defaultFree) is a function object which SFINAE tests if its arguments can be passed to a callable called defaultFree. This permits 1 argument to be passed.



                        Function objects are not functions. A lambda is a function object, as is the return type of OVERLOADS_OF. Function objects can be passed in and their overloaded operator() considered; they can remember their default parameters, do SFINAE, etc.



                        So when you pass the lambda, both possibilities are legal. When you pass a function, it becomes a function pointer to the no-default-argument call to the function, and that unambiguously does not accept 1 parameter.




                        To fix your problem you should make one overload look better than the other.



                        One approach is to use ...:



                        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);



                        the trick is that int is preferred over ... when you pass 0.



                        You could also be more explicit, and generate traits like "can be called with 1 argument", "can be called with 2 arguments", then state that the 1 arg case is only enabled when you can be called with 1 but not 2 arguments.



                        There are also tag dispatching based overload resolution ordering techniques.






                        share|improve this answer






















                          up vote
                          3
                          down vote










                          up vote
                          3
                          down vote









                          A function name is not the name of a C++ object.



                          Instead, when you use a function name, a bunch of conversions occur. Overload resolution is done based on the calling or (implicit or explicit) casting context, and a pointer is produced.



                          Default arguments on a function are part of overload resolution. They are never passed as part of a function pointer type.



                          You can create a simple wrapper that turns a function name into a function object:



                          #define RETURNS(...) 
                          noexcept(noexcept(__VA_ARGS__))
                          -> decltype(__VA_ARGS__)
                          return __VA_ARGS__;

                          #define OVERLOADS_OF(...)
                          (auto&&...args)
                          RETURNS( __VA_ARGS__( decltype(args)(args)... ) )


                          with this, you can modify your code:



                          return func(OVERLOADS_OF(defaultFree));


                          and get the default arguments to be considered by func and SFINAE to result in ambiguity.



                          Now, OVERLOADS_OF(defaultFree) is a function object which SFINAE tests if its arguments can be passed to a callable called defaultFree. This permits 1 argument to be passed.



                          Function objects are not functions. A lambda is a function object, as is the return type of OVERLOADS_OF. Function objects can be passed in and their overloaded operator() considered; they can remember their default parameters, do SFINAE, etc.



                          So when you pass the lambda, both possibilities are legal. When you pass a function, it becomes a function pointer to the no-default-argument call to the function, and that unambiguously does not accept 1 parameter.




                          To fix your problem you should make one overload look better than the other.



                          One approach is to use ...:



                          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);



                          the trick is that int is preferred over ... when you pass 0.



                          You could also be more explicit, and generate traits like "can be called with 1 argument", "can be called with 2 arguments", then state that the 1 arg case is only enabled when you can be called with 1 but not 2 arguments.



                          There are also tag dispatching based overload resolution ordering techniques.






                          share|improve this answer












                          A function name is not the name of a C++ object.



                          Instead, when you use a function name, a bunch of conversions occur. Overload resolution is done based on the calling or (implicit or explicit) casting context, and a pointer is produced.



                          Default arguments on a function are part of overload resolution. They are never passed as part of a function pointer type.



                          You can create a simple wrapper that turns a function name into a function object:



                          #define RETURNS(...) 
                          noexcept(noexcept(__VA_ARGS__))
                          -> decltype(__VA_ARGS__)
                          return __VA_ARGS__;

                          #define OVERLOADS_OF(...)
                          (auto&&...args)
                          RETURNS( __VA_ARGS__( decltype(args)(args)... ) )


                          with this, you can modify your code:



                          return func(OVERLOADS_OF(defaultFree));


                          and get the default arguments to be considered by func and SFINAE to result in ambiguity.



                          Now, OVERLOADS_OF(defaultFree) is a function object which SFINAE tests if its arguments can be passed to a callable called defaultFree. This permits 1 argument to be passed.



                          Function objects are not functions. A lambda is a function object, as is the return type of OVERLOADS_OF. Function objects can be passed in and their overloaded operator() considered; they can remember their default parameters, do SFINAE, etc.



                          So when you pass the lambda, both possibilities are legal. When you pass a function, it becomes a function pointer to the no-default-argument call to the function, and that unambiguously does not accept 1 parameter.




                          To fix your problem you should make one overload look better than the other.



                          One approach is to use ...:



                          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);



                          the trick is that int is preferred over ... when you pass 0.



                          You could also be more explicit, and generate traits like "can be called with 1 argument", "can be called with 2 arguments", then state that the 1 arg case is only enabled when you can be called with 1 but not 2 arguments.



                          There are also tag dispatching based overload resolution ordering techniques.







                          share|improve this answer












                          share|improve this answer



                          share|improve this answer










                          answered 2 hours ago









                          Yakk - Adam Nevraumont

                          170k18173350




                          170k18173350



























                               

                              draft saved


                              draft discarded















































                               


                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function ()
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52370305%2fsfinae-on-functions-with-default-parameters-free-function-vs-operator%23new-answer', 'question_page');

                              );

                              Post as a guest













































































                              Comments

                              Popular posts from this blog

                              What does second last employer means? [closed]

                              Installing NextGIS Connect into QGIS 3?

                              One-line joke