Selecting a Member Function Using Different Enable_If Conditions

Selecting a member function using different enable_if conditions

enable_if works because the substitution of a template argument resulted in an error, and so that substitution is dropped from the overload resolution set and only other viable overloads are considered by the compiler.

In your example, there is no substitution occurring when instantiating the member functions because the template argument T is already known at that time. The simplest way to achieve what you're attempting is to create a dummy template argument that is defaulted to T and use that to perform SFINAE.

template<typename T>
struct Point
{
template<typename U = T>
typename std::enable_if<std::is_same<U, int>::value>::type
MyFunction()
{
std::cout << "T is int." << std::endl;
}

template<typename U = T>
typename std::enable_if<std::is_same<U, float>::value>::type
MyFunction()
{
std::cout << "T is not int." << std::endl;
}
};

Edit:

As HostileFork mentions in the comments, the original example leaves the possibility of the user explicitly specifying template arguments for the member functions and getting an incorrect result. The following should prevent explicit specializations of the member functions from compiling.

template<typename T>
struct Point
{
template<typename... Dummy, typename U = T>
typename std::enable_if<std::is_same<U, int>::value>::type
MyFunction()
{
static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
std::cout << "T is int." << std::endl;
}

template<typename... Dummy, typename U = T>
typename std::enable_if<std::is_same<U, float>::value>::type
MyFunction()
{
static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
std::cout << "T is not int." << std::endl;
}
};

Selecting a method using different enable_if conditions

if constexpr can be a means for partial compilation, but the condition inside if must always be compilable. In your case v1<St> and v2<St> only exist when St is of the right type, hence the errors.

You can use specialization of variable templates instead, e.g. like this

template<typename, typename = void>
constexpr bool is_v1 = false;

template<typename St>
constexpr bool is_v1<St, enable_if_t<is_same_v<decltype(St::header), Header>>> = true;

template<typename, typename = void>
constexpr bool is_v2 = false;

template<typename St>
constexpr bool is_v2<St, enable_if_t<is_same_v<decltype(St::order_header), OrderHeader>>> = true;

template<typename St>
bool validate(St s)
{
if constexpr (is_v1<St>)
{
return f1(s.header);
}
else if constexpr (is_v2<St>)
{
return f2(s.order_header);
}

return true;
}

Now is_v1<St> and is_v2<St> always return some value (either true or false), and the code should compile1.


1 There's also a typo in f2(): oh.i == 1 and oh.j == 1 should be oh.i == 1 && oh.j == 1.

Also note h.i == 1 ? true : false is tautologous, just h.i == 1 is enough.

std::enable_if to conditionally compile a member function

SFINAE only works if substitution in argument deduction of a template argument makes the construct ill-formed. There is no such substitution.

I thought of that too and tried to use std::is_same< T, int >::value and ! std::is_same< T, int >::value which gives the same result.

That's because when the class template is instantiated (which happens when you create an object of type Y<int> among other cases), it instantiates all its member declarations (not necessarily their definitions/bodies!). Among them are also its member templates. Note that T is known then, and !std::is_same< T, int >::value yields false. So it will create a class Y<int> which contains

class Y<int> {
public:
/* instantiated from
template < typename = typename std::enable_if<
std::is_same< T, int >::value >::type >
T foo() {
return 10;
}
*/

template < typename = typename std::enable_if< true >::type >
int foo();

/* instantiated from

template < typename = typename std::enable_if<
! std::is_same< T, int >::value >::type >
T foo() {
return 10;
}
*/

template < typename = typename std::enable_if< false >::type >
int foo();
};

The std::enable_if<false>::type accesses a non-existing type, so that declaration is ill-formed. And thus your program is invalid.

You need to make the member templates' enable_if depend on a parameter of the member template itself. Then the declarations are valid, because the whole type is still dependent. When you try to call one of them, argument deduction for their template arguments happen and SFINAE happens as expected. See this question and the corresponding answer on how to do that.

Simple SFINAE Problem conditionally declaring member function

The notes from cppreference show similar and explains why it does not work:

A common mistake is to declare two function templates that differ only in their default template arguments. This does not work because the declarations are treated as redeclarations of the same function template (default template arguments are not accounted for in function template equivalence).

/* WRONG */

struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
typename = std::enable_if_t<std::is_integral<Integer>::value>
>
T(Integer) : m_type(int_t) {}

template <typename Floating,
typename = std::enable_if_t<std::is_floating_point<Floating>::value>
>
T(Floating) : m_type(float_t) {} // error: treated as redefinition
};

/* RIGHT */

struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
std::enable_if_t<std::is_integral<Integer>::value, int> = 0
>
T(Integer) : m_type(int_t) {}

template <typename Floating,
std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
>
T(Floating) : m_type(float_t) {} // OK
};

Applying the same fix to your code, makes it output the desired B :

#include <type_traits>
#include <iostream>

template<typename A, typename B>
class Test {
public:

template<typename X = A,std::enable_if_t<std::is_same<X, void>::value, int> = 0>
void foo() {
std::cout << "A";
}

template<typename X=A,std::enable_if_t<!std::is_same<X, void>::value, int> = 0>
void foo() {
std::cout << "B";
}


};

In your code the two function templates differed only in their default arguments. After the fix the second parameter is either int = 0 or a substition failure.

enable_if to conditionally include member functions

std::enable_if needs to depend on a parameter of the member template itself.

template <typename TagType>
class foo
{
public:
template <typename U = TagType>
typename std::enable_if<
std::is_base_of<std::bidirectional_iterator_tag,
U>::value,
foo>::type
operator --() {
return *this;
}
};

SFINAE will work as expected.

int main() {
foo<std::random_access_iterator_tag> f;
foo<std::forward_iterator_tag> f2;
--f; // fine
--f2;
}

main.cpp:24:3: error: no match for 'operator--' (operand type is 'foo<std::forward_iterator_tag>')

--f2;

enable_if in function members for void and inheritance

When you instantiated Test<void>, you also instantiated the declarations of all of it's member functions. That's just basic instantiation. What declarations does that give you? Something like this:

void execute(void);
void execute(<ill-formed> t);

If you were expecting SFINAE to silently remove the ill-formed overload, you need to remember that the S stands for "substitution". The substitution of template arguments into the parameters of a (member) function template. Neither execute is a member function template. They are both regular member functions of a template specialization.

You can fix it in a couple of ways. One way would be to make those two templates, do SFINAE properly, and let overload resolution take you from there. @YSC already shows you how.

Another way is to use a helper template. This way you get your original goal, for a single member function to exist at any one time.

template<typename T>
struct TestBase {
void execute(T t) { }
};

template<>
struct TestBase<void> {
void execute() { }
};

template<class T>
struct Test : private TestBase<T> {
using TestBase<T>::execute;
};

You can choose whichever works best for your needs.


To address your edit. I think the second approach actually fits your needs better.

template<typename T>
struct TestBase : Base {
void execute(T t) override { }
};

template<>
struct TestBase<void> : Base {
void execute() override { }
};

TestBase is the middle man that accomplishes what you seem to be after.

How to use std::enable_if with a condition which itself depends on another condition?

While I wrote the below on Coliru, @dyp already showed the important part in his comment. The following is what will work and what is, IMHO, quite readable:

template<
class T,
typename=typename std::enable_if<is_bar<T>::value>::type,
typename=typename std::enable_if<std::is_integral<typename baz_type<T>::type>::value>::type
>
int foo(T x)
{
return 7;
}

template<
class T,
typename=typename std::enable_if<!is_bar<T>::value>::type
>
int foo(T x)
{
return 13;
}

Live example

With C++14, one would use std::enable_if_t to make it even shorter.

enable_if with return type for class member function

There is no SFINAE involved here because bool_check_return are not templates themselves. They are just regular overloaded functions that differ only in return type. Making them templates would solve the problem by allowing only one of them:

template<bool enabled = boolean>
constexpr typename std::enable_if<enabled, int>::type bool_check_return(
int s) const noexcept {
return s + 1;
}

template<bool enabled = boolean>
constexpr typename std::enable_if<not enabled, int>::type bool_check_return(
int s) const noexcept {
return s;
}


Related Topics



Leave a reply



Submit