C++ Templates: Conditionally Enabled Member Function

C++ templates: conditionally enabled member function

SFINAE works in a template function. In the context of template type substitution, substitution failure in the immediate context of the substitution is not an error, and instead counts as substitution failure.

Note, however, that there must be a valid substitution or your program is ill formed, no diagnostic required. I presume this condition exists in order that further "more intrusive" or complete checks can be added to the language in the future that check the validity of a template function. So long as said checks are actually checking that the template could be instantiated with some type, it becomes a valid check, but it could break code that expects that a template with no valid substitutions is valid, if that makes sense. This could make your original solution an ill formed program if there is no template type you could pass to the operator== function that would let the program compile.

In the second case, there is no substitution context, so SFINAE does not apply. There is no substitution to fail.

Last I looked at the incoming Concepts proposal, you could add requires clauses to methods in a template object that depend on the template parameters of the object, and on failure the method would not be considered for overload resolution. This is in effect what you want.

There is no standards-compliant way to do this under the current standard. The first attempt is one that may people commonly do, and it does compile, but it is technically in violation of the standard (but no diagnostic of the failure is required).

The standards compliant ways I have figured out to do what you want:

Changing one of the parameters of the method to be a reference to a never-completed type if your condition fails. The body of the method is never instantiate if not called, and this technique prevents it from being called.

Using a CRTP base class helper that uses SFINAE to include/exclude the method depending on an arbitrary condition.

template <class D, class ET, class=void>
struct equal_string_helper {};

template <class D, class ET>
struct equal_string_helper<D,ET,typename std::enable_if<std::is_same<ET, char>::value>::type> {
D const* self() const { return static_cast<D const*>(this); }
bool operator==(const std::string & other) const {
if (self()->count_ == other.length())
{
return memcmp(self()->elements_, other.c_str(), other.length()) == 0;
}
return false;
}
};

where we do this:

template <typename ElementType>
class WorkingSimpleVector:equal_string_helper<WorkingSimpleVector,ElementType>

We can refactor the conditional machinery out of the CRTP implementation if we choose:

template<bool, template<class...>class X, class...>
struct conditional_apply_t {
struct type {};
};

template<template<class...>class X, class...Ts>
struct conditional_apply_t<true, X, Ts...> {
using type = X<Ts...>;
};
template<bool test, template<class...>class X, class...Ts>
using conditional_apply=typename conditional_apply_t<test, X, Ts...>::type;

Then we split out the CRTP implementation without the conditional code:

template <class D>
struct equal_string_helper_t {
D const* self() const { return static_cast<D const*>(this); }
bool operator==(const std::string & other) const {
if (self()->count_ == other.length())
{
return memcmp(self()->elements_, other.c_str(), other.length()) == 0;
}
return false;
}
};

then hook them up:

template<class D, class ET>
using equal_string_helper=conditional_apply<std::is_same<ET,char>::value, equal_string_helper_t, D>;

and we use it:

template <typename ElementType>
class WorkingSimpleVector: equal_string_helper<WorkingSimpleVector<ElementType>,ElementType>

which looks identical at point of use. But the machinery behind was refactored, so, bonus?

Conditionally enabled member functions in C++17

Your code is well-formed.

The linked answer refers to [temp.res.general]/8.1:

The validity of a template may be checked prior to any instantiation. ... The program is ill-formed, no diagnostic required, if:

— no valid specialization can be generated for a template ... and the template is not instantiated, ...

Here, the "template" that we're talking about is Foo.

I believe this can be interpreted in two ways:

(1) We can consider Wrapper<A>::Foo and Wrapper<B>::Foo to be the same template (for every A,B). Then, the existence of such a template argument for Wrapper that makes the condition in enable_if_t true is alone enough to make the code well-formed.

(2) We can also consider Wrapper<A>::Foo and Wrapper<B>::Foo to be different templates (for A != B). Then, if there existed such a template argument for Wrapper for which it's impossible to instantiate Foo, your code would be ill-formed NDR. But it never happens, because you can always specialize HasFoo to be true for every template argument!

In any case, I argue that (1) is the intended interpretation. The purpose of [temp.res.general]/8.1 is not to get in your way, but to help you by validating templates early when possible. I've never seen compilers use the second interpretation.

Conditionally enable member function depending on template parameter

Whenever you separate a function's declaration and definition, whether it is a template function or not, default argument values can only be in the declaration, not in the definition. So, simply remove the default values from foo's definition, eg:

#include <iostream>

template <size_t N>
class A
{
public:
template <size_t n = N, std::enable_if_t<(n == 3)>* = nullptr> int foo(int a);
};

template<size_t N>
template <size_t n, std::enable_if_t<(n == 3)>*>
int A<N>::foo(int a)
{
return a * 4;
}

Online Demo

C++ conditional template member function

If you can guarantee that all types appear only once, then the following should work:

template<typename... Ts>
class Executor {
using TupleOfCallback = std::tuple<std::function<void(Ts)>...>;
public:
Executor(const std::function<void(Ts)>&... func);

template<class E>
std::enable_if_t<(std::is_same_v<Ts, E*> || ...)>
Exec(E* entity) {
std::get<std::function<void(E*)>>(m_Callbacks)(entity);
}

template<class E>
std::enable_if_t<!(std::is_same_v<Ts, E*> || ...)>
Exec(E* entity)
{ }

public:
TupleOfCallback m_Callbacks;
};

The basic idea is to use fold-expression to detect whether E* is included in Ts..., thereby enabling the corresponding function.

Demo.

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.

How to conditionally add a function to a class template?

You can do it with std::enable_if in the following mode

template <std::size_t r = nrows, std::size_t c = ncols>
typename std::enable_if<r == c>::type setIdentity ()
{ /* do something */ }

A full example

#include <type_traits>

template<typename T, std::size_t nrows, std::size_t ncols>
class Matrix
{
T data[nrows][ncols];

public:
T& operator ()(std::size_t i, std::size_t j)
{ return data[i][j]; }

template <std::size_t r = nrows, std::size_t c = ncols>
typename std::enable_if<r == c>::type setIdentity ()
{ /* do something */ }
};

int main()
{
Matrix<int, 3, 3> mi3;
Matrix<int, 3, 2> mnoi;

mi3.setIdentity();
// mnoi.setIdentity(); error

return 0;
}

--- EDIT ---

As pointed in a comment by Niall (regarding the TemplateRex's answer, but my solution suffer from the same defect) this solution can be circonvented expliciting the number of rows and columns in this way

mi3.setIdentity<4, 4>();

(but this isn't a real problem (IMHO) because mi3 is a square matrix and setIdentity() could work with real dimensions (nrows and ncols)) or even with

mnoi.setIdentity<4, 4>()

(and this is a big problem (IMHO) because mnoi isn't a square matrix).

Obviously there is the solution proposed by Niall (add a static_assert; something like

    template <std::size_t r = nrows, std::size_t c = ncols>
typename std::enable_if<r == c>::type setIdentity ()
{
static_assert(r == nrows && c == ncols, "no square matrix");

/* do something else */
}

or something similar) but I propose to add the same check in std::enable_if.

I mean

template <std::size_t r = nrows, std::size_t c = ncols>
typename std::enable_if< (r == c)
&& (r == nrows)
&& (c == ncols)>::type setIdentity ()
{ /* do something */ }

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.

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;

compile-time conditional member function call in C++

Firstly, you can't use SFINAE like that - the template type parameter needs to be on the function, not the class.

A full solution looks like this:

template<class T> class A
{
private:
template <class S>
typename std::enable_if<std::is_floating_point<S>::value>::type a_member() {
std::cout << "Doing something";
}

template <class S>
typename std::enable_if<!std::is_floating_point<S>::value>::type a_member() {
//doing nothing
}

public:
void another_member()
{
a_member<T>();
}
};

int main() {
A<int> AInt;
AInt.another_member();//doesn't print anything

A<float> AFloat;
AFloat.another_member();//prints "Doing something"
}


Related Topics



Leave a reply



Submit