Search code examples
c++c++11perfect-forwardinguniversal-reference

limit universal reference to a single type using aliasing


I have a member function of a class in which I'd like to use perfect forwarding for one of the parameters. However, the function being forwarded to only accepts a single argument of type c2Type, so I'd like the calling function also to only accept c2Type arguments, but obviously have to keep the universal reference to do the forwarding. It seems it can be accomplished using a default template parameter like so:

class c2Type 
{
    // some data members...
};

template<typename T, typename isC2Type = typename std::enable_if<
               std::is_same<c2Type, typename std::decay<T>::type>::value>::type>
    void configurationMessageHandler(T&& message)
{
    // some stuff...
   mapAddress(std::forward<c2Type>(message));   
}

mapAddress(c2Type&& message)
{
    // do stuff...
};  

However, I need to check for this type on several member functions, and also such a long template seems unfriendly and unreadable. What I'd like to is create an alias for isC2Type like

template<typename T>
using isC2Type = typename std::enable_if<
           std::is_same<c2Type, typename std::decay<T>::type>::value>::type;

which I thought would make the configurationMessageHandler template look like

template<typename T, isC2Type<T>>

but that doesn't compile. How can I properly use aliases in this case?


Solution

  • I hope this can help you.

    template<class U, class T, 
             class= std::enable_if_t<std::is_same<std::decay_t<T>, U>::value, T>>
    using LimitTo = T;
    
    template<class T>
    void configurationMessageHandler(LimitTo<c2Type,T>&& message){
        // some stuff...
        mapAddress(std::forward<T>(message)); 
        //!! Use T because the reference of c2Type maybe has cv-qualify
    }
    

    Even when there are many parameters, for example:

    void foo(int); 
    template<class A, class B, class C>
    void foo(LimitTo<int,A>&& , LimitTo<float,B>&& , LimitTo<bool,C>&& );
    template<class T>
    void foo(LimitTo<string,T>&& ); 
    

    However, there are some pitfalls with this trick:

    1. Be careful that in some cases it won't work. This leads to fatal errors in some compilers. I don't know why.

        template<class...Args> 
        void foo(LimitTo<double,Args>&&... args){}
      
    2. Use default parameters to avoid ambiguities, e.g. with regards to such constructors:

        template<class T>
        Ctor(LimitTo<string,T>&&,string* =nullptr) {} //string* or anything else
      
        template<class T>
        Ctor(LimitTo<double,T>&&, double* =nullptr) {}
      

    Inherited constructors will cause some problems as default parameters can't be inherited. So change it to:

     template<class T>
     Ctor(LimitTo<string,T>&&,string*) {} 
     template<class T>
     Ctor(LimitTo<double,T>&&, double*) {}