c++templatesc++20

Convert template argument to template template


I have following, simplified code. The question is, is there any way to recognize A::v_ and value parameter as an container type and call the right convert function? Or the rework need to be done? In case of rework do you have any suggestions how to deal with such problem most efficiently?

template <typename A, typename B>
B convert(A a)
{
   return B{a};
}

template <template <typename A> typename CA, typename A, typename B>
B convert(CA<A> a)
{
   return B{a.first()};
}

template <typename A, template <typename B> typename CB, typename B>
CB<B> convert(A a)
{
   CB<B> cb; cb.push_back(static_cast<B>(a));
   return cb;
}

template <template <typename A> typename CA, typename A, template <typename B> typename CB, typename B>
CB<B> convert(CA<A> a);

template <typename T>
struct A
{
   T v_;

   template <typename U>
   void assign(const A<U>& value)
   {
      // How to recognize if "value" and "this->v_" is an container
      // and call right "convert" function
      // if constexpr (???)
      //    v_ = convert<value container, value element, this->v_ container, this->v_ element>(value);
      // else if constexpr (???)
      //    v_ = convert<value type, this->v_ container, this->v_ element>(value);
      // else if constexpr (???)
      //    v_ = convert<value container, value element, this->v_ type>(value);
      // else
      //    v_ = convert<value type, this->v_ type>(value);
   }
};

int main(int argc, const char *argv[])
{
   A<int> i;
   A<std::vector<int> > vi;

   vi.assign(i);
}

I tried to search for converting template arguments to template-template but with no luck.


Solution

  • CB<B> convert(A a) and B convert(A a) are similar and can IMO be regrouped in the later. Reorder template parameter to have deducible type at the end would also simplify the call.

    With all that, A would simply be:

    template <typename T>
    struct A
    {
        T v_{};
    
        template <typename U>
        void assign(const A<U>& value)
        {
            v_ = convert<T>(value.v_);
        }
    };
    

    Then for the different case, using tag dispatching:

    // Customization points
    template <typename To, typename From>
    To convert_to(std::type_identity<To>, From a)
    {
       return To{a};
    }
    
    template <typename To, template <typename> class Cont, typename T>
    To convert_to(std::type_identity<To>, Cont<T> a)
    {
       return To{a.front()};
    }
    
    // Same implementation than convert_to(std::type_identity<To>, From a)
    // so might be omitted here.
    template <template <typename> class Cont, typename T, typename From>
    Cont<T> convert_to(std::type_identity<Cont<T>>, From a)
    {
       return Cont<T>{a}; 
    }
    
    // Better would be to check class capabilities (have back()/begin()/end()/...)
    // instead of a generic Cont<T>.
    // Below A<T> won't fulfil the requirement for example.
    template <template <typename> class Cont, typename T, template <typename> class Cont2, typename T2>
    Cont<T> convert_to(std::type_identity<Cont<T>>, Cont2<T2> a)
    {
       return Cont<T>{a.back()};
    }
    
    // Entry point
    template <typename To, typename From>
    To convert(From a)
    {
       return convert_to(std::type_identity<To>{}, a);
    }
    

    Demo

    or if constexpr (C++17) (and concept C++20):

    template <typename T>
    concept Container = requires (T t)
    {
        t.begin();
        t.end();
        t.front();
        t.back();
        //...
    };
    
    template <typename To, typename From>
    To convert(From a)
    {
        if constexpr (Container<To>) {
            if constexpr (Container<From>) {
                return To{a.front()};
            } else {
                return To{a};
            }
        } else {
            if constexpr (Container<From>) {
                return To{a.back()};
            } else {
                return To{a};
            }
        }
    }
    

    Demo