17

I have a template class and a member function print() to print the data.

template<typename T>
class A
{
public:
   T data;
   void print(void) 
   { 
      std::cout << data << std::endl; 
   }
   // other functions ...
};

Then, I want to either print scalar data or vector data, so I give a specialized definition and get a compiler error.

template<typename T>
void A<std::vector<T>>::print(void) // template argument list error
{
   for (const auto& d : data) 
   {
      std::cout << d << std::endl;
   }
}

Question: Why does this member function specialization get an error? What is the correct way to define a print function for a vector?

Solution 1: I have tested the following definition.

template<typename T>
class A<std::vector<T>>
{
public:
   std::vector<T> data;
   void print(void) {   // OK
      // ... 
   } 
}

This one worked, but I have to copy the other member functions into this specialized class.


EDIT:

Solution 2: To prevent copy all the other member functions, I define a base class containing the common member functions and inherit from the base class:

template<typename T>
class Base
{
public:
   T data;
   // other functions ...
};

template<typename T>
class A : public Base<T>
{
public:
   void print(void) 
   {
      std::cout << this->data << std::endl;
   }
};

template<typename T>
class A<std::vector<T>> : public Base<std::vector<T>>
{
public:
   void print(void) 
   {
      for (const auto& d : this->data)
      {
         std::cout << d << std::endl;
      }
   }
};

This solution works well. Are there some better or more conventional solutions?

JeJo
  • 30,635
  • 6
  • 49
  • 88
kfckfckf
  • 173
  • 1
  • 6
  • 5
    Does this answer your question? [c++ template partial specialization member function](https://stackoverflow.com/questions/15374841/c-template-partial-specialization-member-function) – G. Sliepen Jul 06 '20 at 07:20
  • I think the example in my question is not a partial specialization. – kfckfckf Jul 06 '20 at 07:35
  • 1
    *Are there some better or more **conventional solutions**?*: This could be opinion based answer. You should go for those which suits to your situation. If you have many members to do like this, a specialized class would be a better option as you could collectively see them in one class definition. – JeJo Jul 06 '20 at 08:20

4 Answers4

11

Why does this member function specialization get error?

When you instantiate the template class A for example A<std::vector<int>>, the template parameter T is equal to std::vector<int>, not std::vector<T>, and this a specialization case of the function. Unfortunately this can not be done with member functions as mentioned in the comments.


Are there some better solutions?

Yes; In you could use if constexpr with a trait to check the std::vector, like this.

#include <type_traits> // std::false_type, std::true_type
#include <vector>

// traits for checking wether T is a type of std::vector<>
template<typename T> struct is_std_vector final : std::false_type {};
template<typename... T> struct is_std_vector<std::vector<T...>> final : std::true_type {};

template<typename T>
class A /* final */
{
    T mData;

public:  
    // ...constructor  

    void print() const /* noexcept */
    {
        if constexpr (is_std_vector<T>::value) // when T == `std::vector<>`
        {
            for (const auto element : mData)
                std::cout << element << "\n";
        }
        else // for types other than `std::vector<>` 
        {
            std::cout << mData << std::endl;
        }
    }
};

(See Live Online)

This way you keep only one template class and the print() will instantiate the appropriate part according to the template type T at compile time.


If you don not have access to C++17, other option is to SFINAE the members(Since ).

#include <type_traits> // std::false_type, std::true_type, std::enbale_if
#include <vector>

// traits for checking wether T is a type of std::vector<>
template<typename T> struct is_std_vector final : std::false_type {};
template<typename... T> struct is_std_vector<std::vector<T...>> final : std::true_type {};

template<typename T>
class A /* final */
{
    T mData;

public:  
    // ...constructor  

    template<typename Type = T> // when T == `std::vector<>`
    auto print() const -> typename std::enable_if<is_std_vector<Type>::value>::type
    {
        for (const auto element : mData)
                std::cout << element << "\n";
    }

    template<typename Type = T> // for types other than `std::vector<>`
    auto print() const -> typename std::enable_if<!is_std_vector<Type>::value>::type
    {
        std::cout << mData << std::endl;
    }
};

(See Live Online)


What if I have more other data types like self-define vector classes or matrices? Do I have to define many is_xx_vector?

You can check the type is a specialization of the provided one like as follows. This way you can avoid providing many traits for each type. The is_specialization is basically inspired from this post

#include <type_traits> // std::false_type, std::true_type
#include <vector>

// custom MyVector (An example)
template<typename T> struct MyVector {};

template<typename Test, template<typename...> class ClassType>
struct is_specialization final : std::false_type {};

template<template<typename...> class ClassType, typename... Args>
struct is_specialization<ClassType<Args...>, ClassType> final : std::true_type {};

And the print function could be in :

void print() const /* noexcept */
{
   if constexpr (is_specialization<T, std::vector>::value)// when T == `std::vector<>`
   {
      for (const auto element : mData)
         std::cout << element << "\n";
   }
   else if constexpr (is_specialization<T, ::MyVector>::value)  // custom `MyVector`
   {
      std::cout << "MyVector\n";
   }
   else  // for types other than `std::vector<>` and custom `MyVector`
   {
      std::cout << mData << std::endl;
   }
}

(See Live Online)

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • About your answer to my question "Why does this member function specialization get error?", I want to know why a class can be specialized with template, like `template class A>{};`, but a member function cannot be specialized with template. The link you provided is about the **partial** specialization, but in my case, it's a **full** specialization with a template template parameter. – kfckfckf Jul 06 '20 at 08:31
  • About your solution, what if I have more other data types like self-define vector classes or matrices? Do I have to define many `is_xx_vector`? – kfckfckf Jul 06 '20 at 08:36
  • @kfckfckf Yes need to unfortunately define those, if you have to do something specific to those type: https://godbolt.org/z/EuTmUE. If the operations are same like `std::vector` you need only one trait to check weather the type is one of those. This post will help for that.[`is_one_of`](https://stackoverflow.com/questions/62578701/divorce-a-parameter-pack-in-a-class-template/62578803#62578803) – JeJo Jul 06 '20 at 09:03
5

You need to implement a template class that uses a vector as template parameter. This worked for me.

template<typename T>
class A
{
public:
    T data;

    void print(void) {
        std::cout << "Data output" << std::endl;
    }
    // other functions ...
};

template <typename T>
class A<std::vector<T>>
{
public:
    std::vector<T> data;

    void print() {
        for (auto i : data) {
            std::cout << "Vector output" << std::endl;
        }
    }
};
bozali
  • 83
  • 1
  • 4
  • 4
    And what if besides `print` you had 10 more member functions? – Evg Jul 06 '20 at 07:40
  • 4
    No, it can be a good solution in some situations. For example, if you have many `print`-like functions. Then it is reasonable to implement them in a separate class and use full specialization. – Evg Jul 06 '20 at 08:54
3

You could always use named tag dispatching to check if type provided by template user is vector.

A<std::vector<T>> notation won't work as you both try to take into account that T is type and vector of types which is contradicting with itself.

Below is code I used named tag dispatching as solution to your problem:

#include <iostream>
#include <vector>
#include <type_traits>

using namespace std;

template<typename T> struct is_vector : public std::false_type {};

template<typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type {};

template<typename T>
class A
{
public:
    T data;
    void print(std::true_type) {
        for (auto& a : data) { std::cout << a << std::endl; } 
    }
    void print(std::false_type) {
        std::cout << data << std::endl;
    }
    void print() {
        print(is_vector<T>{});
    }
};
    
int main()
{
    A<int> a;
    a.data = 1;
    a.print();
    A<std::vector<int>> b;
    b.data = { 1, 2 ,3 ,4 ,5 };
    b.print();
    return 0;
}

Succesfully compiled with https://www.onlinegdb.com/online_c++_compiler

Based on answer: Check at compile-time is a template type a vector

Advent
  • 140
  • 15
  • 1
    Thanks for your answer. Your answer seems to be the same as @JeJo 's. So my question is the same: What if I have more other data types like self-define vectors? Do I have to define many `is_xx_vector` class? – kfckfckf Jul 06 '20 at 08:53
  • It depends what you are trying to achieve. In case you want to have many vector-like classes, you might want to inherit from std::vector itself and it should find out that this new type is vector. Also, you might want to restrict using class template for certain T types so you won't use template for type you haven't prepared methods for. – Advent Jul 06 '20 at 09:00
2

You can dispatch printing to another member function (static or not). For example:

template<typename T>
class A {
public:
    T data;
    
    void print() const {
        print_impl(data);
    }
    
private:
    template<class S>
    static void print_impl(const S& data) {
        std::cout << data;
    }

    template<class S, class A>
    static void print_impl(const std::vector<S, A>& data) {
        for (const auto& d : data)
            std::cout << d;
    }
};
Evg
  • 25,259
  • 5
  • 41
  • 83
  • 3
    Nice, but, same problem you mentioned in the other answer. What if OP has more functions where needed to be consider the same approach? More static internal functions right! – UserUsing Jul 06 '20 at 07:50
  • 1
    @UsingCpp, agree. If I had many member functions that need differentiation based on the same criterion, I'd likely introduce a separate class (in addition to `A`) and put them there. – Evg Jul 06 '20 at 07:52
  • The solution works since the STL vector has two template parameters. What if I use a self-defined vector class that only takes one template parameter, like `template class vector{};`? – kfckfckf Jul 06 '20 at 08:16
  • @kfckfckf, then you just remove the second parameter. If you want to be very generic you can detect `vector`-like class based on its member functions and member types with `std::void_t` trick. – Evg Jul 06 '20 at 08:34
  • @Evg Thanks for your answer and reply. I have tested your solution and it works! Just one more question: Can I use non-static functions instead? If so, how can I define them? – kfckfckf Jul 06 '20 at 08:48
  • 2
    @kfckfckf, just remove `static` and add `const` at the end. – Evg Jul 06 '20 at 08:55