33

In template meta programming, one can use SFINAE on the return type to choose a certain template member function, i.e.

template<int N> struct A {
  int sum() const noexcept
  { return _sum<N-1>(); }
private:
  int _data[N];
  template<int I> typename std::enable_if< I,int>::type _sum() const noexcept
  { return _sum<I-1>() + _data[I]; }
  template<int I> typename std::enable_if<!I,int>::type _sum() const noexcept
  { return _data[I]; }
};

However, this doesn't work on constructors. Suppose, I want to declare the constructor

template<int N> struct A {
   /* ... */
   template<int otherN>
   explicit(A<otherN> const&); // only sensible if otherN >= N
};

but disallow it for otherN < N.

So, can SFINAE be used here? I'm only interested in solutions which allow automatic template-parameter deduction, so that

A<4> a4{};
A<5> a5{};
A<6> a6{a4};  // doesn't compile
A<3> a3{a5};  // compiles and automatically finds the correct constructor

Note: this is a very simplified example where SFINAE may be overkill and static_assert may suffice. However, I want to know whether I can use SFINAE instead.

Walter
  • 44,150
  • 20
  • 113
  • 196

5 Answers5

32

You can add a defaulted type argument to the template:

template <int otherN, typename = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • See boost's [enable_if documentation](http://www.boost.org/libs/utility/enable_if.html) for further details. – thiton Jan 30 '13 at 11:54
  • 3
    Note that this won't work as is if you have two such constructors with different conditions. (And if you don't have overloads, you probably should be using static_assert, but was already dealt with in the question) – R. Martinho Fernandes Jan 30 '13 at 12:00
  • 1
    @KerrekSB: Because the first typename clarifies that enable_if::type must be a typename, I think. – thiton Jan 30 '13 at 12:02
  • 1
    @KerrekSB: It is optional? Really? Well, from the context it should be clear that a typename has to follow, but I did not know that C++ actually allows the removal of the 2nd typename here. Are you sure? Perhaps you've tried this with a more forgiving compiler that doesn't actually do a proper two-phase lookup? – sellibitze Jan 30 '13 at 12:09
  • 1
    @R.MartinhoFernandes: static assertion is different, though. The point of `enable_if` is to get the correct behaviour for `std::is_constructible, A>::value`. – Kerrek SB Jan 30 '13 at 12:34
  • @KerrekSB yeah, I know, that. This conflict between the desire for proper error messages and proper behaviour of traits is annoying. I am actually in the middle of writing an answer about it (not for this question)... – R. Martinho Fernandes Jan 30 '13 at 12:37
  • @KerrekSB Thanks! I didn't consider this solution, because I somehow thought that defaulted template parameters were only allowed for class templates... – Walter Jan 30 '13 at 12:46
  • 1
    @Walter : That was true for C++03, but C++11 allows them for function templates as well. – ildjarn Feb 04 '13 at 17:53
  • 1
    @R.MartinhoFernandes _Note that this won't work as is if you have two such constructors with different conditions._ Is there a way to achieve this? I've only found tag dispatching to a third constructor. – gnzlbg Feb 14 '14 at 10:43
  • @R.MartinhoFernandes: Didn't we say the other day that a template alias cannot be used for SFINAE because it's not substitution failure if a template alias cannot be instantiated? I.e. `using enable_if_t = ...` isn't usable for SFINAE? – Kerrek SB Feb 14 '14 at 11:07
  • @KerrekSB that's irrelevant, though. Using a non-type parameter works, and that's the crux of the pattern. The alias is just a matter of convenience. (And no, I don't remember that) – R. Martinho Fernandes Feb 14 '14 at 11:09
  • @KerrekSB afaik the alias should work, there are bug reports about in gcc and llvm bugzilla tho. – gnzlbg Feb 14 '14 at 13:37
  • @R.MartinhoFernandes The link to flamingdangerzone.com no longer works. Can you provide the method in other ways? – Walter Apr 05 '19 at 05:46
  • Notice that the condition is misleading as `otherN == N` would not use that method but the copy constructor. – Jarod42 Apr 08 '19 at 22:45
  • But then need parent for parsing :-) – Jarod42 Apr 09 '19 at 00:32
21

There are many ways to trigger SFINAE, being enable_if just one of them. First of all:

What is std::enable_if ?

It's just this:

template<bool, class T=void> enable_if{ typedef T type; };
template<class T> enable_if<false,T> {};
template<bool b, class T=void> using enable_if_t = typename enable_f<b,T>::type;

The idea is to make typename enable_if<false>::type to be an error, hence make any template declaration containing it skipped.

So how can this trigger function selection?

Disabling functions

The idea is making the declaration erroneous in some part:

By return type

template<class Type>
std::enable_if_t<cond<Type>::value,Return_type> function(Type);

By a actual parameter

template<class Type>
return_type function(Type param, std::enable_if_t<cond<Type>::value,int> =0) 

By a template parameter

template<class Type, 
    std::enable_if_t<cond<Type>::value,int> =0> //note the space between > and =
return_type function(Type param) 

Selecting functions

You can parametrise different alternatives with tricks like this:

tempplate<int N> struct ord: ord<N-1>{};
struct ord<0> {};

template<class T, std::enable_if<condition3, int> =0>
retval func(ord<3>, T param) { ... }

template<class T, std::enable_if<condition2, int> =0>
retval func(ord<2>, T param) { ... }

template<class T, std::enable_if<condition1, int> =0>
retval func(ord<1>, T param) { ... }

template<class T> // default one
retval func(ord<0>, T param) { ... }

// THIS WILL BE THE FUCNTION YOU'LL CALL
template<class T>
retval func(T param) { return func(ord<9>{},param); } //any "more than 3 value"

This will call the first/second/third/fourth function if condition3 is satisfied, than condition2 than condition1 than none of them.

Other SFINAE triggers

Writing compile-time conditions can be either a matter of explicit specialization or a matter of unevaluated expression success/failure:

for example:

template<class T, class = void>
struct is_vector: std::false_type {};
template<class X>
struct is_vector<vector<X> >:: std::true_type {};

so that is_vector<int>::value is false but is_vecttor<vector<int> >::value is true

Or, by means of introspection, like

template<class T>
struct is_container<class T, class = void>: std::false_type {};

template<class T>
struct is_container<T, decltype(
  std::begin(std::declval<T>()),
  std::end(std::declval<T>()),
  std::size(std::declval<T>()),
  void(0))>: std::true_type {};

so that is_container<X>::value will be true if given X x, you can compile std::begin(x) etc.

The trick is that the decltype(...) is actually void (the , operator discards the previous expressions) only if all the sub-expressions are compilable.


There can be even many other alternatives. Hope between all this you can find something useful.

strager
  • 88,763
  • 26
  • 134
  • 176
Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
9

The accepted answer is good for most cases, but fails if two such constructor overloads with different conditions are present. I'm looking for a solution in that case too.

Yes: the accepted solution works but not for two alternative constructor as, by example,

template <int otherN, typename = typename std::enable_if<otherN == 1>::type>
explicit A(A<otherN> const &);

template <int otherN, typename = typename std::enable_if<otherN != 1>::type>
explicit A(A<otherN> const &);

because, as stated in this page,

A common mistake is to declare two function templates that differ only in their default template arguments. This is illegal because default template arguments are not part of function template's signature, and declaring two different function templates with the same signature is illegal.

As proposed in the same page, you can go around this problem applying SFINAE, modifying the signature, to the type of a value (not type) template parameter as follows

template <int otherN, typename std::enable_if<otherN == 1, bool>::type = true>
explicit A(A<otherN> const &);

template <int otherN, typename std::enable_if<otherN != 1, bool>::type = true>
explicit A(A<otherN> const &);
max66
  • 65,235
  • 10
  • 71
  • 111
  • says `error: expected a qualified name after 'typename'` in `template ,bool> =true> constexpr arg(U&&v) : value{v}, name{"opcode"}{}` – kyb Mar 15 '20 at 19:26
  • @kyb - it's difficult to say something useful without a complete example but... maybe with `std::` before `enable_if_t` and before `is_same_v` ? – max66 Mar 15 '20 at 19:42
  • I am using namespace std. looks like i should ask a different Q. – kyb Mar 16 '20 at 06:46
  • @kyb - yes: a new question with a complete example, please. – max66 Mar 16 '20 at 11:06
8

In C++11, you can use a defaulted template parameter:

template <int otherN, class = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);

However, if your compiler doesn't support defaulted template parameters yet, or you need multiple overloads, then you can use a defaulted function parameter like this:

template <int otherN>
explicit A(A<otherN> const &, typename std::enable_if<otherN >= N>::type* = 0);
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
3

With C++20 you can use the requires keyword

With C++20 you can get rid of SFINAE.

The requires keyword is a simple substitute for enable_if!

Note that the case where otherN == N is a special case, as it falls to the default copy ctor, so if you want to take care of that you have to implement it separately:

template<int N> struct A {
   A() {}    

   // handle the case of otherN == N with copy ctor
   explicit A(A<N> const& other) { /* ... */ }

   // handle the case of otherN > N, see the requires below
   template<int otherN> requires (otherN > N)
   explicit A(A<otherN> const& other) { /* ... */ }

   // handle the case of otherN < N, can add requires or not
   template<int otherN>
   explicit A(A<otherN> const& other) { /* ... */ }
};

The requires clause gets a constant expression that evaluates to true or false deciding thus whether to consider this method in the overload resolution, if the requires clause is true the method is preferred over another one that has no requires clause, as it is more specialized.

Code: https://godbolt.org/z/RD6pcE

Amir Kirsh
  • 12,564
  • 41
  • 74