Search code examples
c++template-meta-programmingconstexprmove-semanticsperfect-forwarding

c++ : how to remove cv-qualifiers of a type to access class functions?


Here an example:

#include <iostream>

template<typename T, 
         typename ... Args>
void print(T&& t, Args&& ... args)
{
    // line where compilation fails when the A::run is called
    if constexpr (std::is_invocable_v<decltype(&T::display),T*,Args...>)
      {
          t.display(std::forward<Args>(args)...);
      }
    else 
    {
        std::cout << "not applicable !" << std::endl;    
    }
}

template<typename T>
class A 
{
    public:

    A(T&& t):t_(t){}

    template <typename... Args>
    void run(Args&& ... args)
    {
        print<T,Args...>(t_,std::forward<Args>(args)...);
    }

    T t_;
};

template <typename T> A(T&) -> A<T&>;
template <typename T> A(T&&) -> A<T>;

class B 
{
  public:
  B(int value):value_(value){} 
  void display(int a, int b)
  {
      std::cout << value_ << " " 
                << a << " " 
                << b << std::endl; 
  }
  int value_;
};

int main()
{
    int v1=10;
    int v2=20;

    B b1{1};
    A a1{b1};
    a1.t_.display(v1,v2);

    A a2{B(2)};
    a2.t_.display(v1,v2);

    //a1.run(v1,v2); // (1)
    //a2.run(v1,v2); // (2)
    //a1.run(v1);

    return 0;
}

The code above compiles and runs fine. But if the 3 last lines (calls to run()) are un-commented, the following compilation error occurs:

(1)

main.cpp:7:48: error: ‘display’ is not a member of ‘B&’

if constexpr (std::is_invocable_v<decltype(&T::display),T*,Args...>)

(2)

main.cpp:27:25: error: no matching function for call to ‘print(B&, int&, int&)’

    print<T,Args...>(t_,std::forward<Args>(args)...);

Note :

template <typename T> A(T&) -> A<T&>;
template <typename T> A(T&&) -> A<T>;

explained here:

c++ copy (reference) constructor and move constructor of class cohabitation


Solution

  • Problem (1) and (2) are different problems.

    Problem (1) come from the fact that in the following std::is_invocable_v

    template<typename T, 
             typename ... Args>
    void print(T&& t, Args&& ... args)
    {
        if constexpr (std::is_invocable_v<decltype(&T::display),T*,Args...>)
        //  no T but std::decay_t<T> ...............^...........^
    

    instead of T (that can be a reference) you need the "decayed" type

    I propose

    template <typename T, typename ... Args>
    void print (T && t, Args && ... args)
     {
       using U = std::decay_t<T>;
    
       if constexpr ( std::is_invocable_v<decltype(&U::display), U*, Args...> )
          t.display(std::forward<Args>(args)...);
       else 
          std::cout << "not applicable !" << std::endl;    
    }
    

    Problem (2) come from the fact that explicating the template parameters in the following print() call

    template <typename... Args>
    void run(Args&& ... args)
    {
        print<T,Args...>(t_,std::forward<Args>(args)...);
    }
    

    you impede the correct template deduction.

    Suggestion: let template deduction, and perfect forwarding, works and call the function as follows

        print(t_,std::forward<Args>(args)...);