Search code examples
c++c++17variadic-templatessfinae

Template specialization and selection in variadic template class


I've a variadic templated class with some template specializations. I would like, for some specialization, to enable them conditionally (with std::enable_if, so I added a additional unused template parameter).

Here is a code example:

#include <tuple>
#include <iostream>

template<int v,typename, typename...>
struct A;

template<int v, typename...Ts, typename...Us>
struct A<v,void,std::tuple<Ts...>, Us...> {
   void print() {
    std::cout << "A: Tuple selected" << std::endl;
   }
};

template<int v, typename T, typename...Us>
struct A<v,void,T, Us...> {
   void print() {
    std::cout << "A: one element selected" << std::endl;
   }
};

template<int v,typename, typename...>
struct B;

template<int v, typename...Ts, typename...Us>
struct B<v,std::enable_if_t<v!=11>,std::tuple<Ts...>, Us...> {
   void print() {
    std::cout << "B: Tuple selected" << std::endl;
   }
};

template<int v, typename T, typename...Us>
struct B<v,std::enable_if_t<v==12>,T, Us...> {
   void print() {
    std::cout << "B: one element selected" << std::endl;
   }
};

template<int v, typename...>
struct C;

template<int v, typename...Ts, typename...Us>
struct C<v,std::enable_if_t<v!=11,std::tuple<Ts...>>, Us...> {
   void print() {
    std::cout << "C: Tuple selected" << std::endl;
   }
};

template<int v, typename T, typename...Us>
struct C<v, T, Us...> {
   void print() {
    std::cout << "C: one element selected" << std::endl;
   }
};

int main()
{
   struct A<12,void,std::tuple<int>> a;
   a.print();
   struct B<12,void,std::tuple<int>> b;
   b.print();
   struct C<12,std::tuple<int>> c;
   c.print();
   return 0;
}

Instantiation for a works (and the tuple specialization is indeed chosen).

Instantiation for b fails due to ambiguous template instatiation. With gcc:

$ g++ -std=gnu++17 -Wall -Wextra toto5.cpp
toto5.cpp: In function ‘int main()’:
toto5.cpp:61:38: error: ambiguous template instantiation for ‘struct B<12, void, std::tuple<int> >’
   61 |    struct B<12,void,std::tuple<int>> b;
      |                                      ^
toto5.cpp:25:8: note: candidates are: ‘template<int v, class ... Ts, class ... Us> struct B<v, typename std::enable_if<(v != 11), void>::type, std::tuple<_UTypes ...>, Us ...> [with int v = 12; Ts = {int}; Us = {}]’
   25 | struct B<v,std::enable_if_t<v!=11>,std::tuple<Ts...>, Us...> {
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
toto5.cpp:32:8: note:                 ‘template<int v, class T, class ... Us> struct B<v, typename std::enable_if<(v == 12), void>::type, T, Us ...> [with int v = 12; T = std::tuple<int>; Us = {}]’
   32 | struct B<v,std::enable_if_t<v==12>,T, Us...> {
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
toto5.cpp:61:38: error: aggregate ‘B<12, void, std::tuple<int> > b’ has incomplete type and cannot be defined
   61 |    struct B<12,void,std::tuple<int>> b;
      |                                      ^

For C, the error occurs when reading the template itself (no need to instantiate):

$ g++ -std=gnu++17 -Wall -Wextra toto5.cpp
toto5.cpp:42:8: error: template parameters not deducible in partial specialization:
   42 | struct C<v,std::enable_if_t<v!=11,std::tuple<Ts...>>, Us...> {
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
toto5.cpp:42:8: note:         ‘Ts’

What can be done?

Note: I know that the condition in std::enable_if are not useful, but this is just an example here.

One way would be to use the B solution, but changing the std::enable_if condition to disable the second template specialization, but how to say "not a tuple of any kind"? std::is_same does not seem useful here.

clang++ gives me the same kinds of errors in all these cases.


Solution

  • For specialization B, you need to ensure that the conditions to std::enable_if are orthogonal. In your version, if you supply v=12 both conditions v!=11 and v==12 yield true, meaning that both versions are enabled. That is the reason why you get the ambiguous instantiation error. The following compiles fine (https://godbolt.org/z/csaTWMd9v):

    #include <tuple>
    #include <iostream>
    
    template<int v,typename, typename...>
    struct B;
    
    template<int v, typename...Ts, typename...Us>
    struct B<v,std::enable_if_t<v!=11>,std::tuple<Ts...>, Us...> {
       void print() {
        std::cout << "B: Tuple selected" << std::endl;
       }
    };
    
    template<int v, typename T, typename...Us>
    struct B<v,std::enable_if_t<v==11>,T, Us...> {
       void print() {
        std::cout << "B: one element selected" << std::endl;
       }
    };
    
    int main()
    {
       struct B<12,void,std::tuple<int>> b;
       b.print();
    }
    

    Update: As asked in the comments, a way to check if a certain template parameter is a tuple of any kind can be done as follows (also compare e.g. this post). Is this closer what you want to achieve? (https://godbolt.org/z/dq85fM7KK)

    #include <tuple>
    #include <iostream>
    
    template <typename>
    struct is_tuple : std::false_type
    { };
    
    template <typename... T>
    struct is_tuple<std::tuple<T...>> : std::true_type
    { };
    
    
    template<int v,typename, typename...>
    struct B;
    
    template<int v, typename...Ts, typename...Us>
    struct B<v, std::enable_if_t<v!=11>, std::tuple<Ts...>, Us...> {
       void print() {
        std::cout << "B: Tuple selected" << std::endl;
       }
    };
    
    template<int v, typename T, typename...Us>
    struct B<v,std::enable_if_t<v==12 && !is_tuple<T>::value>, T, Us...> {
       void print() {
        std::cout << "B: one element selected" << std::endl;
       }
    };
    
    
    int main()
    {
       B<12, void, std::tuple<int>>{}.print(); // Tuple selected
       B<12, void, int>{}.print(); // one element selected
    }