Search code examples
c++templatesusing

How to bring base template class type in current scope when classes type names match?


I am making statistical data managing software and have met with some gcc specific problems. So with huge effort I have managed to recreate problem in minimal example:

// generic statistic with complicated managing logic and single calculation
// funciton. there could be multiple classes like this
template<class T> 
class statistic {
public:
    using value_type = T;
    virtual value_type f(int param) = 0;
};

// macro for defining a lot of statistics. 
// it could take the name for different statistic class
#define STAT(NAME, TYPE) \
    class NAME##_impl: public statistic<TYPE> { \
    public: \
        using typename statistic<TYPE>::value_type; \
 \
        value_type f(int param) override; \
    } NAME; \

template<class T> 
class vec {
public:
    using value_type = T;

    // (error) trying to make statistic follow the vector type
    STAT(mult_by_2, value_type);

    // this statistic should be int not regarding the vec type
    STAT(integer_statistic, int);

    value_type vec_data;
};

template<class T>
typename vec<T>::mult_by_2_impl::value_type vec<T>::mult_by_2_impl::f(int param) { 
    // value type in statistic<T> that was brought in current class scope
    value_type var = 2*param;
    return var; 
}

template<class T>
typename vec<T>::integer_statistic_impl::value_type vec<T>::integer_statistic_impl::f(int param) { 
    return 1; 
}

int main(int argc, char** argv) {
    vec<double> v;
    return 0;
}

This code complies nicely with clang++ using.cpp -std=c++17, but g++ using.cpp -std=c++17 complains:

using.cpp:15:49: error: declaration of ‘using statistic<T>::value_type’ changes meaning of ‘value_type’ [-Wchanges-meaning]
   15 |                 using typename statistic<TYPE>::value_type; \
      |                                                 ^~~~~~~~~~
using.cpp:26:9: note: in expansion of macro ‘STAT’
   26 |         STAT(mult_by_2, value_type);
      |         ^~~~
using.cpp:26:25: note: used here to mean ‘using vec<T>::value_type = T’
   26 |         STAT(mult_by_2, value_type);
      |                         ^~~~~~~~~~
using.cpp:15:42: note: in definition of macro ‘STAT’
   15 |                 using typename statistic<TYPE>::value_type; \
      |                                          ^~~~
using.cpp:23:15: note: declared here
   23 |         using value_type = T;
      |               ^~~~~~~~~~

As I have understood, the STAT(mult_by_2, value_type) expands to class definition with the using typename statistic<value_type>::value_type in it. But the first value_type is from vec and second value_type is from the statistic</*first value_type*/>. It is pretty confusing, I know.

The question is, How to make a statistic inside vec that will be of the value_type type?


Solution

  • For code reuse (implementations of abstract base classes) you can use CRTP/mixin (which will avoid diamond inheritance in larger code bases). E.g. like this :

    #include <iostream>
    #include <typeinfo>
    
    // your interfaces (abstract baseclasses)
    
    template<class T>
    class statistic
    {
    public:
        virtual T f(int param) = 0;
    };
    
    template<class T>
    class statistic2
    {
    public:
        virtual T g(int param) = 0;
    };
    
     // Reusable inmplementations for your interfaces 
    
    template<typename Derived, typename T>
    class statistic_impl : public statistic<T>
    {
    public:
        T f(T param) override
        {
            std::cout << typeid(T).name() << " statistic_impl<" << typeid(Derived).name() << ">::f(int)\n";
            return param;
        }
    };
    
    template<typename Derived, typename T>
    class statistic_impl2 : public statistic2<T>
    {
    public:
        T g(T param) override
        {
            std::cout << typeid(T).name() << " statistic_impl2<" << typeid(Derived).name() << ">::f(int)\n";
            return 2 * param;
        }
    };
    
    
    // And then your vec class
    // deriving from its CRTP/mixin implementations. 
    template<class T>
    class vec :
        public statistic_impl<vec<T>, T>,
        public statistic_impl2<vec<T>, T>
    {
    };
    
    int main() 
    {
        vec<int> v;
    
        v.f(1);
        v.g(2);
    
        return 0;
    }