Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class template specialization within template class

Re-factoring legacy code I want to merge separate template classes/structs that are related to each other (to avoid namespace pollution).

Nested (below) is a helper class for MyStruct, which i want to move into MyStruct.

But I cannot make this work:

#include <type_traits>
#include <iostream>

struct YES {} ;
struct NO {};

template <typename TYPE>
struct MyStruct
{

    template <typename TYPE_AGAIN = TYPE, typename SELECTOR = NO>
    struct Nested
    {
        static void Print(void)
        {
            std::cout << "MyStruct::Nested<bool = false>::Print()" << std::endl;
        }
    };


    template <>
    struct Nested<TYPE, typename std::enable_if<std::is_integral<TYPE>::value, YES>::type>
    {
        static void Print(void)
        {
           std::cout << "MyStruct::Nested<bool = true>::Print()" << std::endl;
        }
    }; 

};

the compiler complains:

g++ -O0 -g3 -Wall -c -fmessage-length=0 -std=c++11 -MMD -MP -MF"main.d" -MT"main.d" -o "main.o" "../main.cpp"
In file included from ../main.cpp:8:0:
../MyStruct.h:31:12: error: explicit specialization in non-namespace scope ‘struct MyStruct<TYPE>’
  template <>
            ^
make: *** [main.o] Error 1

Actually it also bothers me to have to include

<typename TYPE_AGAIN = TYPE>

But without, there are even more complains:

g++ -O0 -g3 -Wall -c -fmessage-length=0 -std=c++11 -MMD -MP -MF"main.d" -MT"main.d" -o "main.o" "../main.cpp"
In file included from ../main.cpp:8:0:
../MyStruct.h:31:12: error: explicit specialization in non-namespace scope ‘struct MyStruct<TYPE>’
  template <>
            ^
../MyStruct.h:32:9: error: template parameters not used in partial specialization:
  struct Nested<typename std::enable_if<std::is_integral<TYPE>::value, YES>::type>
     ^
../MyStruct.h:32:9: error:         ‘TYPE’
make: *** [main.o] Error 1
like image 938
Frank Bergemann Avatar asked Mar 11 '23 22:03

Frank Bergemann


2 Answers

You can't specialize templates in non-namespace scope, like a struct in your case.

You would have to put the specializations outside of the struct definition:

template<typename TYPE> template<>
struct MyStruct<TYPE>::Nested<...> {};

But now you have another problem, if you want to specialize a template inside of a template class, you'll have to specialize it for every template class. You can't just specialize one member function, you have to specialize the whole class.

So, you need to do this:

template<> template<>
struct MyStruct<int>::Nested<...> {};

Also, you really don't need SFINAE for this:

template<typename SELECTOR>
struct Nested; // Default invalid SELECTOR

template<>
struct Nested<YES> { /*...*/ };

template<>
struct Nested<NO> { /*...*/ };
like image 102
Rakete1111 Avatar answered Mar 20 '23 15:03

Rakete1111


It seems there are 2 reasons why you can not compile your code:

  • a full specialization of a inner template class can not be declared inside the class. The best solution is to add a dumb defaulted template parameter, and then declare a partial specialization of the inner template class.

  • A template specialization can not directly be disabled with std::enable_if. But we can use the void_t trick to do it. void_t is a c++17 feature, so with earlier c++ standard we need to provides its definition. I took the void_t definition from http://en.cppreference.com/w/cpp/types/void_t.

So you could rewrite your code as follow:

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

 //Crédit: cpp-reference.com
 template<typename... Ts> struct make_void { typedef void type;};
 template<typename... Ts> using void_t = typename make_void<Ts...>::type;

 template <typename TYPE>
 struct MyStruct
 {
     //The third parameter is an unused parameter 
     template <typename TYPE_AGAIN = TYPE, typename SELECTOR = void
               , typename = void>
     struct Nested
     {
         static void Print(void)
         {
             std::cout << "MyStruct::Nested<bool = false>::Print()" << std::endl;
         }
     };

     //We declare a partial specialization, T go to the third parameter. We use the void_t trick here.
     template <class T>
     struct Nested<TYPE,
                  void_t<typename std::enable_if<
                                      std::is_integral<TYPE>::value>::type>
                        ,T>
     {
         static void Print(void)
         {
            std::cout << "MyStruct::Nested<bool = true>::Print()" << std::endl;
         }
     }; 

 };

 int main(){
    MyStruct<int>::Nested<>::Print();
    return EXIT_SUCCESS;
 }
like image 43
Oliv Avatar answered Mar 20 '23 17:03

Oliv