Search code examples
c++functiontemplateslambdamember

template member function resolution fails when declaring const


the code below shows some interesting behavior:

#include <iostream>
using namespace std;
template<class T>
class B{
public:
  void foo(B<T> &x)const;
  template<class F> void foo(F f);
};
template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
template<typename T> template<typename F> void B<T>::foo(F f){cout<<"foo_F"<<endl;}
int main(){
  B<int> a;
  B<int> b;
  b.foo(a);
  b.foo([](){;});
  return(0);
}

my expected output is

foo_B
foo_F

but the actual output is

foo_F
foo_F

it depends on whether void foo(B<T> &x) is declared const. If const is omitted the output is as expected.

Further, if const is added to void foo(F f) the output is as expected as well.

However, void foo(B<T> &x) will not change the this, whereas void foo(F f) will change this. So the current layout is the one required.

Any idea how to resolve this without dropping const is much appreciated.


Solution

  • The issue here is that since void foo(B<T> &x)const; is const qualified, It would have to const qualify the object you are calling the function on. This isn't as exactly as a match as template<class F> void foo(F f); provides as it doesn't need to do that const qualification. That is why it is used for both calls.

    You can fix this by also const qualifying the template version like:

    #include <iostream>
    using namespace std;
    template<class T>
    class B{
    public:
      void foo(B<T> &x)const;
      template<class F> void foo(F f)const;
    };
    template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
    template<typename T> template<typename F> void B<T>::foo(F f)const{cout<<"foo_F"<<endl;}
    int main(){
      B<int> a;
      B<int> b;
      b.foo(a);
      b.foo([](){;});
      return(0);
    }
    

    Which will print

    foo_B
    foo_F
    

    Another option would be to use SFINAE to constrain the template version from excepting B<T>'s. That would look like

    #include <iostream>
    using namespace std;
    template<class T>
    class B{
    public:
      void foo(B<T> &x)const;
      template<class F, std::enable_if_t<!std::is_same_v<B<T>, F>, bool> = true> 
      void foo(F f);
    };
    template<typename T> void B<T>::foo(B<T> &x)const{cout<<"foo_B"<<endl;}
    template<typename T> template<class F, std::enable_if_t<!std::is_same_v<B<T>, F>, bool>>  
    void B<T>::foo(F f){cout<<"foo_F"<<endl;}
    int main(){
      B<int> a;
      B<int> b;
      b.foo(a);
      b.foo([](){;});
      return(0);
    }
    

    and has the same output as the first example.