2

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.

Sedenion
  • 5,421
  • 2
  • 14
  • 42
Vincent
  • 57
  • 6

1 Answers1

2

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
}
Sedenion
  • 5,421
  • 2
  • 14
  • 42
  • Hi, Writing a orthogonal condition is something that I already point at the end of my question. But, then I do not know how to do it: For the tuple specialization, I want to check that v has a certain value. Ok. For for the other case, the default when the parameter pack is not empty, I want to say: not a tuple or v with a different value. I do not know how to express "not a tupple" for the next parameter in the if_enable condition. – Vincent Apr 08 '22 at 19:33
  • And, to be more precise, my initial goal was to check that v has a specific bit set (not a specific value, so I cannot specialize the first parameter for a particular value). – Vincent Apr 08 '22 at 19:43
  • @Vincent I still don't get what your desired behavior is: Should the "Tuple selected" variant be called for `v!=11 && firstArgIsTuple`, while "one element selected" should be called for `v==12 && firstArgIsNotTuple`, and in all other cases you want a compilation error? I have updated my answer with a way to check if some type is a tuple. Is this closer to the desired behavior? – Sedenion Apr 09 '22 at 12:15
  • I've a variadic template. The first (integer) argument is set of options. Then I've several specialized cases to handle the next argument (and the following are handled recursively). When the cases are only based on the type, it works perfectly (as illustrated on my first A example when the tuple case is handled specifically). My problem is when I want to have a specialization based on the type but also on the options in the first argument. I want to specialize the tuple case only when there is an option (a bit) set in the first argument. Else, I want the default case. – Vincent Apr 10 '22 at 16:59
  • Oh, reading too fast, I missed the `is_tuple<>` part that have been added. So, yes, It should work indeed. I just accepted the answer. – Vincent Apr 10 '22 at 19:29