简体   繁体   中英

How do I use std::enable_if to enable or disable constructors depending on template types?

I have the following templated object:

template< typename type_1, typename type_2 > struct result
{
    // I want to enable these two constructors only if type_1 != type_2
    result( type_1 f ) : foo{f} {}
    result( type_2 b ) : bar{b} {}

    // I want to enable this constructor only if type_1 == type_2
    result( type_1 f, type_2 b ) : foo{f}, bar{b} {}

    // Other member functions removed.

    type_1 foo;
    type_2 bar;
};

How do I use std::enable_if to enable or disable the constructors as required?

eg:

This one would have only the first two constructors:

result<string,int> // type_1 != type_2

This one would have only the third constructor:

result<int,int> // type_1 == type_2

This seems working, but I am not sure it is the optimal way

So just add new template parameters with default values to the constructor to enable SFINAE

#include <type_traits>

template< typename type_1, typename type_2 >
struct result
{
    // I want to enable these two constructors only if type_1 != type_2
    template<typename T1 = type_1, typename T2 = type_2>
    result( type_1 f, 
            typename std::enable_if<!std::is_same<T1, T2>::value>::type * = nullptr )
       : foo{f} {}
    template<typename T1 = type_1, typename T2 = type_2>
    result( type_2 b, 
           typename std::enable_if<!std::is_same<T1, T2>::value, int >::type * = nullptr )
       : bar{b} {}                                        /*     ^^^ need this to avoid duplicated signature error with above one*/ 

    // I want to enable this constructor only if type_1 == type_2
    template<typename T1 = type_1, typename T2 = type_2>
    result( type_1 f, type_2 b,
            typename std::enable_if<std::is_same<T1, T2>::value>::type * = nullptr ) 
       : foo{f}, bar{b} {}

    type_1 foo;
    type_2 bar;
};

int main()
{
   result<int, double> r(1);
   result<int, double> r2(1.0);

   result<int, int> r3(1, 2);

   // disbaled
   //result<int, double> r4(1, 2.0);
   //result<int, int> r5(1);
}

Also read: Select class constructor using enable_if

The primary template can serve as a specialization for mistmatched types. For matching types you can partially specialize:

template <typename type_1, typename type_2>
struct result
{
    result( type_1 f ) : foo{f} {}
    result( type_2 b ) : bar{b} {}

    type_1 foo;
    type_2 bar;
};

template <typename type>
struct result<type, type>
{
    result( type f, type b ) : foo{f}, bar{b} {}

    type foo;
    type bar;
};

This is similar to @BryanChen's answer, but cleaner IMO :) You can use inheritance to improve the ambiguity resolution and move the enable_if s to the template arguments of the constructor.

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

using namespace std;

template <int N>
class Disambiguator;

template<>
class Disambiguator<0>{};

template <int N>
class Disambiguator : public Disambiguator<N-1>{};

using Disambiguate = Disambiguator<100>;

template< typename type_1, typename type_2 > struct result
{
  template <typename T, typename U>
  using IsSame = typename enable_if<is_same<T, U>::value>::type;

  template <typename T, typename U>
  using IsNotSame = typename enable_if<!is_same<T, U>::value>::type;

  template <typename T = type_1, typename U = type_2, typename = IsNotSame<T,U>>
  result( type_1 f, Disambiguator<0>) : foo{f} {cout<<"NotSameType"<<endl;}

  template <typename T = type_1, typename U = type_2, typename = IsNotSame<T,U>>
  result( type_2 b, Disambiguator<1>) : bar{b} {cout<<"NotSameType"<<endl;}

  // I want to enable this constructor only if type_1 == type_2
  template <typename T = type_1, typename U = type_2, typename = IsSame<T,U>>
  result( type_1 f, type_2 b ) : foo{f}, bar{b} {cout<<"SameType"<<endl;}

  // Other member functions removed.

  type_1 foo;
  type_2 bar;
};


int main()
{
  result<float, int> c(1.0, Disambiguate{});
  result<float, int> i(0, Disambiguate{});

  result<int, int> j(0, 0);

  result<string, int> s("abc", Disambiguate{});
  result<string, int> si(0, Disambiguate{});

  return 0;
}

EDIT : You can read @Xeo's overload resolution idea here . That's what I have used in the above code.

Another solution (which is more related to How do I use std::enable_if to enable or disable constructors depending on template types? but still on the subject of disabling constructors) is to use a boolean template param that defaults to true:

template <class T, class Unrelated>
class MyClass {
 public:
  // Enable constructor if IsEnabled == True and T != int
  template <bool IsEnabled = true,
            typename std::enable_if<(IsEnabled && !std::is_same<T, int>::value),
                                    int>::type = 0>
  MyClass(T x) {
    cout << "IsNotInt" << endl;
  }

  MyClass(int x) {
    cout << "IsInt" << endl;
  }
};

Since IsEnabled defaults to true, the std::enable_if condition will be checked even though the template param is not used in the constructor. This also allows you to enable/disable constructors based on the value of a class's template param:

template <int N, class Unrelated>
class MyOtherClass {
 public:
  // Enable constructor if IsEnabled == True and N > 0
  template <bool IsEnabled = true,
            typename std::enable_if<(IsEnabled && N > 0), int>::type = 0>
  MyOtherClass(int x) {
    cout << "N > 0" << endl;
  }

  // Enable constructor if IsEnabled == True and N <= 0
  template <bool IsEnabled = true,
            typename std::enable_if<(IsEnabled && N <= 0), int>::type = 0>
  MyOtherClass(int x) {
    cout << "N <= 0" << endl;
  }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM