4

I have a class template with a primary template that is meant to work with any type argument T. However, due to some particular needs, I need to use template specialization like this:

template<typename T>
class foo 
{
private:
    T result;
public:
    void modify_result(T a)
    {
        result = some_operations(a);
    }
};

template<>
class foo<uint64_t> 
{
private:
    uint64_t result;
public:
    // notice: uint32_t not the same as our template argument
    void modify_result(uint32_t a)
    {
        result = some_operations(a);
    }
};

The problem is that I have to create a large number of full specializations for each of my particular cases. If I ever want to modify or add something to the class, I will have to do it for every specialization, which is really bad for maintainability.

I would like to have some sort of type trait that checks the type of T. If T is of a certain type e.g. uint64_t, then we know that the input of the method or certain variables inside the method need to be of type uint32_t. This would enable me to customize my template without additional maintenance costs.

JeJo
  • 30,635
  • 6
  • 49
  • 88

3 Answers3

4

You can define a type trait, and add specialization for each particular type, like:

template <typename T>
struct parameter_type {
    using type = T;
};

template <>
struct parameter_type<uint64_t> {
    using type = uint32_t;
};

Then use it as:

template<typename T>
class foo {
private:
    T result;
public:
    void modify_result(typename parameter_type<T>::type a) {
        result = some_operations(a);
    }
};
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 3
    Note: to avoid having to write `typename parameter_type::type` everywhere, you can also declare a type alias `using parameter = typename parameter_type::type;` in the class, or just `using parameter = parameter_type::type;` since C++20. – Jan Schultke Jun 14 '23 at 08:53
2

In C++17, I would propose the if constexpr, check for each type you wished to handle separately. By doing so, you will be able to see the implementations for the different types in one place and thereby (arguably) maintain the code easily.

template<typename T> class foo 
{
private:
   T result;
public:

   template<typename U>
   void modify_result(U a) 
   {
       if constexpr (std::is_same_v<U, uint64_t>)
       {
           // ... do something special for uint64_t type 'a'.
           result = some_operations(a);
       }
       else if constexpr (std::is_same_v<U, std::string>)
       {
           // ... do something special for std::string type 'a'.
           result = some_operations(a);
       }
       else
       {
           // ... all other types cases!
       }
   } 
};
JeJo
  • 30,635
  • 6
  • 49
  • 88
1

Other answers propose to use a type trait or constexpr if. A third option is to refactor all what depends on the template parameter, but only that, to a base class:

template<typename T>
class foo_base {
private:
    T result;
public:
    void modify_result(T a) {
        result = some_operations(a);
    }
};

template<>
class foo<uint64_t> {
private:
    uint64_t result;
public:
    void modify_result(uint32_t a) {
        result = some_operations(a);
    }
};

template <typename T>
class foo : public foo_base {
    // anything that does not depend on T is here
};

This way you have to write foo only once and can specialize foo_base. Among the three alternatives, I would go for the type trait. The constexpr if is fine, when the part to be specialized is only in limited scope and inheritance is not always the right tool (sloopy speaking, it changes what your type is when you actually want to change only one implementation detail. Though sometimes you want to change what the type is...).

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185