Template Specialization and Enable_If Problems

Template specialization and enable_if problems

Default template arguments are not part of the signature of a function template. So in your example you have two identical overloads of less, which is illegal. clang complains about the redefinition of the default argument (which is also illegal according to §14.1/12 [temp.param]), while gcc produces the following error message:

error: redefinition of 'template<class T, class> bool less(T, T)'

To fix the error move the enable_if expression from default argument to a dummy template parameter

template <class T,
typename std::enable_if<std::is_floating_point<T>::value, int>::type* = nullptr>
bool less(T a, T b) {
// ....
}

template <class T,
typename std::enable_if<std::is_integral<T>::value, int>::type* = nullptr>
bool less(T a, T b) {
// ....
}

Another option is to use enable_if in the return type, though I feel this is harder to read.

template <class T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
less(T a, T b) {
// ....
}

template <class T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
less(T a, T b) {
// ....
}

Template specialization with enable_if

typename enable_if<is_class<T>::value>::type = 0 doesn't make sense, because typename enable_if<is_class<T>::value>::type would refer to void; you can change it to typename enable_if<is_class<T>::value>::type* = nullptr. Then for the full specialization for int, note that test has two template parameters, then

// Declaration
template <typename T, typename enable_if<is_class<T>::value>::type* = nullptr>
void test(T& value);

template <typename T, typename enable_if<!is_class<T>::value>::type* = nullptr>
void test(T& value);

template <>
void test<int, nullptr>(int& value);

// Definition
template <typename T, typename enable_if<is_class<T>::value>::type*>
void test(T& value) {
cout << "Class/struct test" << endl;
}

template <typename T, typename enable_if<!is_class<T>::value>::type*>
void test(T& value) {
cout << "Other types test" << endl;
}

template <>
void test<int, nullptr>(int& value) {
cout << "int test" << endl;
}

LIVE

Or simply put typename enable_if<is_class<T>::value>::type as the return type. e.g.

// Declaration
template <typename T>
typename enable_if<is_class<T>::value>::type test(T& value);

template <typename T>
typename enable_if<!is_class<T>::value>::type test(T& value);

template <>
void test<int>(int& value);

// Definition
template <typename T>
typename enable_if<is_class<T>::value>::type test(T& value) {
cout << "Class/struct test" << endl;
}

template <typename T>
typename enable_if<!is_class<T>::value>::type test(T& value) {
cout << "Other types test" << endl;
}

template <>
void test<int>(int& value) {
cout << "int test" << endl;
}

LIVE

c++ template specialization with std::enable_if not working

The problem is that you're using T in a non-deduced context

template< typename T >
void SetAttribute( const typename std::enable_if< std::is_integral< T >::value >::type& value ) {}
^

Functions are probably the wrong tool for this job (they cannot be partially specialized), a possible workaround if you insist in using functions could be a combination of tag dispatching and specializations

template<class T>
void SetAttribute(const T&, std::true_type) {}

template<class T>
void SetAttribute(const T& value, std::false_type)
{
static_assert(std::is_integral<T>::value, "SetAttribute: wrong type!");
}

template< typename T >
void SetAttribute(const T& value)
{
SetAttribute(value, std::is_integral<T>());
}

template<> void SetAttribute(const bool&) {}

template<> void SetAttribute(const std::wstring&) {}

Example

Quite unreadable if you ask me..

`enable_if` with `enum` template specialization problem

As @bogdan said in the comments, this is most likely a compiler bug. Actually I noticed that it works if you use trailing return types in the out-of-line definitions of your function templates:

template <class A, class B, Logic::Strategy strategy>
auto Logic::computeThings() ->
std::enable_if_t<strategy==Logic::strat_A,int> {
return 0;
}

template <class A, class B, Logic::Strategy strategy>
auto Logic::computeThings() ->
std::enable_if_t<strategy==Logic::strat_B,int> {
return 1;
}

I prefer putting the enable_if in the type of a non-type template parameter with a default argument:

template <class A, class B, Logic::Strategy strategy,
std::enable_if_t<strategy==Logic::strat_A,int> = 0>
int Logic::computeThings() {
return 0;
}

template <class A, class B, Logic::Strategy strategy,
std::enable_if_t<strategy==Logic::strat_B,int> = 0>
int Logic::computeThings() {
return 1;
}

But SFINAE is far too complex of a feature for something so simple. There are much easier ways to do what you are trying to do. Take this example using tag dispatch:

#include <iostream>
#include <type_traits>

class Logic {
public:
enum Strategy { strat_A, strat_B };

template <class A, class B>
int computeThings(std::integral_constant<Strategy, strat_A>);

template <class A, class B>
int computeThings(std::integral_constant<Strategy, strat_B>);
};

template <class A, class B>
int Logic::computeThings(std::integral_constant<Strategy, strat_A>) {
return 0;
}

template <class A, class B>
int Logic::computeThings(std::integral_constant<Strategy, strat_B>) {
return 1;
}

int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int>(
std::integral_constant<Logic::Strategy, Logic::strat_A>{}
)<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int>(
std::integral_constant<Logic::Strategy, Logic::strat_B>{}
)<<std::endl; //outputs 1
return 0;
}

That can be simplified further by getting rid of the enum and directly defining some tag types instead:

class Logic {
public:
class strat_A {};
class strat_B {};

template <class A, class B>
int computeThings(strat_A);

template <class A, class B>
int computeThings(strat_B);
};

template <class A, class B>
int Logic::computeThings(strat_A) { return 0; }

template <class A, class B>
int Logic::computeThings(strat_B) { return 1; }

int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int>(Logic::strat_A{})<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int>(Logic::strat_B{})<<std::endl; //outputs 1
return 0;
}

A more idiomatic and structured approach to the strategy pattern would be to lift the behaviour of the different strategies out of the computeThings function and into the strategy classes themselves:

class Logic {
public:
struct strat_A {
template <class A, class B>
static int computeThings(Logic* self);
};
struct strat_B {
template <class A, class B>
static int computeThings(Logic* self);
};

template <class A, class B, class Strategy>
int computeThings() {
return Strategy::template computeThings<A, B>(this);
}
};

template <class A, class B>
int Logic::strat_A::computeThings(Logic* self) {
return 0;
}

template <class A, class B>
int Logic::strat_B::computeThings(Logic* self) {
return 1;
}

int main() {
Logic mylogic;
std::cout<<mylogic.computeThings<int,int,Logic::strat_A>()<<std::endl; //outputs 0
std::cout<<mylogic.computeThings<int,int,Logic::strat_B>()<<std::endl; //outputs 1
return 0;
}

The Logic* self pointer isn't needed in this example, but would be if the strategies need to access the Logic instance.

template class specialization enable_if and default values

My question is where in the standard is the ordering described for which template is chosen, and why is the instanciation test<T,void> considered as a better match then test<T,T>.

[temp.class.spec.match].

You wrote test<int>, which means that you didn't provide any template argument for the second parameter. Because it has a default parameter, it is chosen, so you actually have test<int, void>.

Now, according to the text linked above, the template parameters are matched to a specialization.

In the first case, the specialization is test<int, void> after evaluation, and so it is an exact match and chosen.

In the second case, the specialization is test<int, int> after evaluation, which is not an exact match and so the primary template is chosen instead of that specialization.

Template specialization with enable_if fails in Clang, works with GCC

In both cases: my_type is specialised to double. Then compare non-specialised version of my_fun

template < >
template <typename Q>
std::enable_if_t<!std::is_same_v<bool, Q>::value, my_type<double>>
// ^ (!)
my_type<double>::my_fun(const my_type<double>& v)

against the fully specialised my_fun:

template < >
template < >
// ^
std::enable_if_t<!std::is_same<bool, double>::value, my_type<double>>
my_type<double>::my_fun<double>(const my_type<double>& v)
// ^

Both of above variants would be legal; you, in contrast, ended up somewhere in between...

GCC accepting this code doesn't look right to me, I join the 'this is a bug' fraction in the comments.

Perhaps even worse: Consider my_type<double>::my_fun<bool> specialisation – it should still exist, shouldn't it?

enable_if for class template specialization with argument other than void

Specializations are irrelevant until the compiler knows which types it is going to use for the primary template.

When you write A<double>, then the compiler looks only at the primary template and sees that you actually mean A<double,void>.

And only then it is looking for specializations. Now, when your specialization is for A<double,int>, then it is not suitable because you asked for A<double,void>.

How does enable_if help select specializations of a class template?

From the reference on partial specialization of class templates:

When a class or variable (since C++14) template is instantiated, and there are partial specializations available, the compiler has to decide if the primary template is going to be used or one of its partial specializations.

If only one specialization matches the template arguments, that specialization is used

In this case, if the 2nd argument of the specialization is well-formed, it is chosen, precisely because it is a specialization, and not the primary template.

In case the 2nd template argument is not well-formed, then SFINAE kicks in. In particular:

When substituting the explicitly specified or deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.

and

The following type errors are SFINAE errors:

attempting to use a member of a type, where
the type does not contain the specified member

How this is done, i.e. how exactly the compiler discards the specialization, instead of giving an error, is not specified; the compiler is just required to do the right thing.

Specializing a template method with enable_if

SFINAE doesn't work over (only) the template parameters of the class/structs.

Works over template methods whit conditions involving template parameters of the method.

So you have to write

   template <typename U = T>
std::enable_if_t<std::is_void<U>::value> call()
{ function(); }

template <typename U = T>
std::enable_if_t<!std::is_void<U>::value> call(T type)
{ function(type); }

or, if you want to be sure that U is T

   template <typename U = T>
std::enable_if_t< std::is_same<U, T>::value
&& std::is_void<U>::value> call()
{ function(); }

template <typename U = T>
std::enable_if_t<std::is_same<U, T>::value
&& !std::is_void<U>::value> call(T type)
{ function(type); }

p.s.: std::enable_if_t is a type so doesn't require typename before.

p.s.2: you tagged C++11 but your example use std::enable_if_t, that is C++14, and std::is_void_v, that is C++17



Related Topics



Leave a reply



Submit