Can the class member can be changed depending on the template args in c++

Viewed 91

I want the class member can be changed depending on the template args. I want something like follow

template<int value>
class MyClass
{
public:
   void print()
   {
      // using the member
      std::cout << sizeData() << std::endl;
      for (int i=0;i<sizeData();i++)
      {
         std:cout << data[i] << std::endl;
      }
   }
   static int sizeData()
   {
     #if value == 1
        return 3;
     #endif
     #if value == 2
        return 6;
     #endif
   }
   static int sizeArray()
   {
     #if value == 1
        return 40;
     #endif
     #if value == 2
        return 200;
     #endif
   }
private:
   #if value == 1
      int data[3];
      const static int array[40];
   #endif
   #if value == 2
      int data[6];
      const static int array[200];
   #endif
}

I do not know can it be implemented in c++.

Thanks for your time.

add

Many sir already give the answer in C++11 and C++17. Thanks all your's advises.

If the code can be settled in C++98, it will be perfect. Because my code should run on a platform which only support C++98.

6 Answers

You can use std::conditional to pick which member you would like to have like

template<int value>
class MyClass
{
public:
   static int sizeData()
   {
       return (value == 1) ? 3 : 6; // needs to be improved
   }
   static int sizeArray()
   {
       return sizeof(array) / sizeof(array[0]);  // size of array divided by size of element is then number of elements in the array
   }
private:

   std::conditional_t<value == 1, int[3], int[6]> data;
   const static std::conditional_t<value == 1, int[40], int[200]> array;

};

While std::conditional is not a part of C++98, it's implemented only using C++98 C++, so you can make you own using the possible implementation from the reference site link. You can see that working with

#include <iostream>

template<bool B, class T, class F>
struct conditional { typedef T type; };
 
template<class T, class F>
struct conditional<false, T, F> { typedef F type; };

template<int value>
class MyClass
{
public:
    static int sizeData()
    {
       return (value == 1) ? 3 : 6; // needs to be improved
    }
    static int sizeArray()
    {
       return sizeof(array) / sizeof(array[0]);
    }
private:

    typename conditional<value == 1, int[3], int[6]>::type data;
    const static typename conditional<value == 1, int[40], int[200]>::type array;

};

int main()
{
    MyClass<1> m;
    std::cout << m.sizeData();
}

in this live example.


This kind of falls apart for sizeData since data is not static but the function is. To avoid code duplication, instead of using std::conditional, we can use the enum trick to get compile time constants for the sizes of the array and use that like

#include <iostream>

template<int value>
class MyClass
{
public:
    static int sizeData()
    {
       return data_size; // now improved
    }
    static int sizeArray()
    {
       return array_size;
    }
private:
    enum { data_size = (value == 1) ? 3 : 6 }; // this is required to be a compile time constant
    enum { array_size = (value == 1) ? 40 : 200 }; // these need to become before the array members
    int data[data_size]; // less verbose
    const static int array[array_size];
};

int main()
{
    MyClass<1> m;
    std::cout << m.sizeData();
}
    
    

You need to move the conditional logic to a localized place. static inline constexpr variables are your friends here, if C++17 is an option:

template<int value>
class MyClass
{
private:
   inline static constexpr int data_size = (value == 1) ? 3 : 6;
   inline static constexpr int array_size = (value == 1) ? 40 : 200;

public:
   static int sizeData() { return data_size; }
   static int sizeArray() {return array_size; }

private:
      int data[data_size];
      const static int array[array_size];  
};

If the logic for computing the size needs to be more complicated, you can always write a constexpr function to compute it given a value as a (non-template) parameter.

Since you deal with C++98 then you have to use template specialization:

template<int value>
class MyClass
{
};

template<>
class MyClass<1>
{
public:
   int sizeData()
   {
       return 3;
   }

   int sizeArray()
   {
       return 40;
   }
private:
   int data[3];
   const static int array[40];
};

template<>
class MyClass<2>
{
public:
   int sizeData()
   {
       return 6;
   }

   int sizeArray()
   {
       return 200;
   }
private:
   int data[6];
   const static int array[200];
};

template<int value>
class Child : public MyClass<value>
{
public:
    int calc()
    {
        // will only work and compile for value 1, 2
        return this->sizeData() + this->sizeArray();
    }
};

You can use if constexpr and immediately evaluated constexpr lambdas.

Disclaimer: it is C++ 17 only, need extra brackets that enclose the lambda in order to compile, and I really wouldn't recommend to use this code as it is. It's purpose is demonstration only!

template<int value>
class MyClass
{
public:
   static int sizeData()
   {
     if constexpr(value == 1)
        return 3;
     
     if constexpr (value == 2)
        return 6;     
   }
   static int sizeArray()
   {
     if constexpr (value == 1)
        return 40;
     
     if constexpr (value == 2)
        return 200;
   }
private:
    int data[([](int v) constexpr { if (v == 1) return 3; if (v==2) return 6; }(value))];
    const static int array[([](int v) constexpr { if (v == 1) return 40; if (v==2) return 200; }(value))]; 
};

int main()
{
   MyClass<1> m1;
   MyClass<2> m2;
}

As of C++17 you can use constexpr if

template<int value>
class MyClass
{
public:
    MyClass() : data() {}

    static constexpr int sizeData() {
        if constexpr (value == 1) { return 3; }
        else if constexpr (value == 2) { return 6; }
        else { return 1; } // default value is '1'
    }
    
    static constexpr int sizeArray() {
        if constexpr (value == 1) { return 40; }
        else if constexpr (value == 2) { return 200; }
        else { return 10; } // default value is '10'
    }
private:
   int data[sizeData()];
   const static int array[sizeArray()];
};

If using e.g. C++14, you could implement custom value traits that maps, at compile time, a given int value to another named (int) constant, and use these value traits within the class template:

template<int value>
constexpr int ValueToDataSizeMapping = 1; // default is '1'

template<>
constexpr int ValueToDataSizeMapping<1> = 3;

template<>
constexpr int ValueToDataSizeMapping<2> = 6;

template<int value>
constexpr int ValueToArraySizeMapping = 10; // default is '10'

template<>
constexpr int ValueToArraySizeMapping<1> = 40;

template<>
constexpr int ValueToArraySizeMapping<2> = 200;

template<int value>
class MyClass
{
public:
    MyClass() : data() {}
private:
   int data[ValueToDataSizeMapping<value>];
   const static int array[ValueToArraySizeMapping<value>];
};

No this can't be implemented in c++. A precompiler "if" can be evalueted over a runtime variable.

You can declare an std::vector to solve this. In this way you can initialize it with the value passed as argument.

At this point the size array and size data function does not need precompiler "if", but just a simple if-else statement.

Related