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.
One of the primary uses of SFINAE can be found through enable_if expressions. enable_if is a set of tools, available in the Standard Library since C++11, that internally use SFINAE. They allow to include or exclude overloads from possible function templates or class template specialization.
David Vandevoorde first introduced the acronym SFINAE to describe related programming techniques. We’re talking here about something related to templates, template substitution rules and metaprogramming… which make it a possibly a scary area!
We moved to an entirely new world, from some complex SFINAE code, some improvements in C++14 and C++17 to a clear syntax in C++20. In this post, we covered theory and examples of SFINAE - a template programming technique that allows you to reject code from the overload resolution sets.
The most basic and intuitive method is to choose a vendor based on cost alone. This is how most construction and trades are chosen. Using price as the sole selection criteria should be limited to situations where the work is relatively well defined and understood by the contractors.
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 &);
There are many ways to trigger SFINAE, being enable_if
just one of them. First of all:
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?
The idea is making the declaration erroneous in some part:
template<class Type> std::enable_if_t<cond<Type>::value,Return_type> function(Type);
template<class Type> return_type function(Type param, std::enable_if_t<cond<Type>::value,int> =0)
template<class Type, std::enable_if_t<cond<Type>::value,int> =0> //note the space between > and = return_type function(Type param)
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With