1

I am trying to selectively enable a method in a templated class only if the tuple template argument has a certain arity. I kept trying all kind of combinations and so far couldn't find a way to get the code below to compile.

Essentially, when I instantiate the class foo with a tuple that has only two elements in it I would like the first "void print()" method to be enabled, while when I instantiate foo with a tuple that has three elements in it I would like the second "void print()" method to be enabled.

Note: This is just an abstract example refined from a more complex problem I am trying to solve. also I am able to get something to work using partial template specialization based on the two specific tuples. However I would really like to understand "enable_if" which I expect will keep my code "simpler".

Thank you

// g++ -std=c++14 a.cc

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

using namespace std;

template <class Tuple>
class foo {
public:
  Tuple t;  
 
  foo(Tuple ta) : t(ta) {
    cout<<"constructor\n";
  }  

  //How do I enable this print ONLY if arity of input tuple is 2  ???
  
  typename std::enable_if< std::tuple_size<Tuple>::value == 2, void >::type
  print(){
    cout<<get<0>(t)<<":"<<get<1>(t)<<"\n";
  }  

  // enable ONLY if arity 3 ???
  typename std::enable_if< std::tuple_size<Tuple>::value == 3, void >::type
  print(){
    cout<<get<0>(t)<<":"<<get<1>(t)<<"::"<<get<1>(t)<<"\n";
  }
};

int main(int argc, char**argv) {  
  typedef tuple<int, double>        t2_type;
  typedef tuple<int, double, float> t3_type;  

  t2_type t2(1,2.0);
  t3_type t3(100,100.0,2.3);  

  foo<t2_type> f2(t2);
  foo<t3_type> f3(t3);  

  f2.print();
  f3.print();
}

For the specific code above here is the compiler error:

% g++ -std=c++14 a.cc 
a.cc:25:3: error: ‘typename std::enable_if<(std::tuple_size<_Tp>::value == 3), void>::type foo<Tuple>::print()’ cannot be overloaded
   print(){
   ^
a.cc:19:3: error: with ‘typename std::enable_if<(std::tuple_size<_Tp>::value == 2), void>::type foo<Tuple>::print()’
   print(){
   ^
a.cc: In instantiation of ‘class foo<std::tuple<int, double, float> >’:
a.cc:38:21:   required from here
a.cc:19:3: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
a.cc: In function ‘int main(int, char**)’:
a.cc:41:6: error: ‘class foo<std::tuple<int, double, float> >’ has no member named ‘print’
   f3.print();
      ^

GCC Details, though this should be compiler independent;

% g++ -v
gcc version 4.9.4 (GCC) 

SOLUTION 1 derived from answers below:

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

using namespace std;                                                                                                                                                                                                                                                                                                    

template <class Tuple>                                                                                                                                                                                                                                                                                                  
class foo {                                                                                                                                                                                                                                                                                                             
public:
  Tuple t;

  foo(Tuple ta) : t(ta) {                                                                                                                                                                                                                                                                                               
    cout<<"constructor\n";                                                                                                                                                                                                                                                                                              
  }

  template <typename T = Tuple>
  typename std::enable_if< std::tuple_size<T>::value == 2, void >::type
  print(){
    cout<<get<0>(t)<<":"<<get<1>(t)<<"\n";
  }

  // enable ONLY if arity 3 ???                                                                                                                                                                                                                                                                                         
  template <typename T = Tuple>
  typename std::enable_if< std::tuple_size<T>::value == 3, void >::type
  print(){
    cout<<get<0>(t)<<":"<<get<1>(t)<<"::"<<get<1>(t)<<"\n";
  }

};

int main(int argc, char**argv) {
  typedef tuple<int, double>        t2_type;
  typedef tuple<int, double, float> t3_type;

  t2_type t2(1,2.0);
  t3_type t3(100,100.0,2.3);

  foo<t2_type> f2(t2);
  foo<t3_type> f3(t3);

  f2.print();
  f3.print();
}
GabrielT
  • 23
  • 5
  • Does this answer your question? [Using enable\_if to prevent declaration?](https://stackoverflow.com/questions/63094844/using-enable-if-to-prevent-declaration) – asmmo Aug 26 '20 at 19:15

2 Answers2

3

You need to make print a template in order to use enable_if. You can just use the class template parameter for this purpose:

template <typename T = Tuple>
    typename std::enable_if< std::tuple_size<T>::value == 2, void >::type
print() {
    cout<<get<0>(t)<<":"<<get<1>(t)<<"\n";
}  

template <typename T = Tuple>
    typename std::enable_if< std::tuple_size<T>::value == 3, void >::type
print() {
    cout<<get<0>(t)<<":"<<get<1>(t)<<"::"<<get<1>(t)<<"\n";
}

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • I don’t know if it’s worth mentioning as part of an answer, but I will at least leave a comment: as of C++20 we can use _requires-clauses_ to conditionally omit the instantiation of a given _non-template_ overloaded member function of a class template, based on a condition solely over the template parameter(s) of the class template. Meaning it would allow us to leave the `print()` overloads as non-template functions. There [are some gritty details](https://stackoverflow.com/a/62661029) w.r.t. how to guarantee omission of ODR-violating instantiations of overloads though. – dfrib Aug 26 '20 at 21:11
  • ([GCC’s take on the above](https://gcc.gnu.org/pipermail/gcc-patches/2020-July/549971.html) is to respect constraints when performing derivative explicit instantiations of comstrained members of a class template for which an explicit instantiation definition has been provided: this interpretation would make the use of requires clauses for non-template members of class template much more useful) – dfrib Aug 26 '20 at 21:16
1

The enable_if can't be based on a class-level template parameter. It has to be deduced at function instantiation for SFINAE to work. Adding a template parameter to print should do the trick. For the size 2 print function it would look like the following

template<typename TupleParam>
typename std::enable_if< std::is_same<Tuple, TupleParam>::value && 
                         std::tuple_size<TupleParam>::value == 2, void >::type
print(){
    cout<<get<0>(t)<<":"<<get<1>(t)<<"\n";
} 
Timmie Smith
  • 413
  • 2
  • 10
  • 2
    without a default for the template parameter one has to expliticly spell it out, as in `f2.print();` or do I miss something? – 463035818_is_not_an_ai Aug 26 '20 at 19:01
  • This solution is working as well with a slight modification for my case: ` template typename std::enable_if< std::is_same::value && std::tuple_size::value == 2, void >::type print(){ ... } ` – GabrielT Aug 26 '20 at 19:18