check if member exists using enable_if
This has become way easier with C++11.
template <typename T> struct Model
{
vector<T> vertices;
void transform( Matrix m )
{
for(auto &&vertex : vertices)
{
vertex.pos = m * vertex.pos;
modifyNormal(vertex, m, special_());
}
}
private:
struct general_ {};
struct special_ : general_ {};
template<typename> struct int_ { typedef int type; };
template<typename Lhs, typename Rhs,
typename int_<decltype(Lhs::normal)>::type = 0>
void modifyNormal(Lhs &&lhs, Rhs &&rhs, special_) {
lhs.normal = rhs * lhs.normal;
}
template<typename Lhs, typename Rhs>
void modifyNormal(Lhs &&lhs, Rhs &&rhs, general_) {
// do nothing
}
};
Things to note:
- You can name non-static data members in
decltype
andsizeof
without needing an object. - You can apply extended SFINAE. Basically any expression can be checked and if it is not valid when the arguments are substituted, the template is ignored.
Checking a member exists, possibly in a base class, C++11 version
Actually, things got much easier in C++11 thanks to the decltype
and late return bindings machinery.
Now, it's just simpler to use methods to test this:
// Culled by SFINAE if reserve does not exist or is not accessible
template <typename T>
constexpr auto has_reserve_method(T& t) -> decltype(t.reserve(0), bool()) {
return true;
}
// Used as fallback when SFINAE culls the template method
constexpr bool has_reserve_method(...) { return false; }
You can then use this in a class for example:
template <typename T, bool b>
struct Reserver {
static void apply(T& t, size_t n) { t.reserve(n); }
};
template <typename T>
struct Reserver <T, false> {
static void apply(T& t, size_t n) {}
};
And you use it so:
template <typename T>
bool reserve(T& t, size_t n) {
Reserver<T, has_reserve_method(t)>::apply(t, n);
return has_reserve_method(t);
}
Or you can choose a enable_if
method:
template <typename T>
auto reserve(T& t, size_t n) -> typename std::enable_if<has_reserve_method(t), bool>::type {
t.reserve(n);
return true;
}
template <typename T>
auto reserve(T& t, size_t n) -> typename std::enable_if<not has_reserve_method(t), bool>::type {
return false;
}
Note that this switching things is actually not so easy. In general, it's much easier when just SFINAE exist -- and you just want to enable_if
one method and not provide any fallback:
template <typename T>
auto reserve(T& t, size_t n) -> decltype(t.reserve(n), void()) {
t.reserve(n);
}
If substitution fails, this method is removed from the list of possible overloads.
Note: thanks to the semantics of ,
(the comma operator) you can chain multiple expressions in decltype
and only the last actually decides the type. Handy to check multiple operations.
Modified code from check if member exists using enable_if is not working
Lhs
is deduced to be a reference type in your example, specifically DataWithNormal&
. Reference types don't have a nested normal
. One way to get around this is to check Lhs
when references are stripped:
decltype(std::remove_reference<Lhs>::type::normal)
Lifting Igor's comment, you can also pretend you have an object, since accessing a member of an object works even with a reference:
decltype(std::declval<Lhs>().normal)
Using SFINAE to check if member exists in class based on a template
The template parameters of the template template parameter can not be used in the body.
template <typename T, typename = void>
struct has_psl : std::false_type
{};
template <template <typename> typename EntryType, typename KeyType>
struct has_psl<EntryType<KeyType>, std::void_t<decltype(EntryType<KeyType>::psl)>> : std::true_type
{};
template <template <typename> typename EntryType, typename KeyType, std::enable_if<has_psl<EntryType<KeyType>>::value>>
bool f(EntryType<KeyType> *buffer, size_t offset, EntryType<KeyType> *new_entry) {
return true;
}
And then,
static_assert(has_psl<int>::value == false);
static_assert(has_psl<RawBytesEntry<int>>::value == true);
Online Demo
Using SFINAE to check if member exists in class based on a template
The template parameters of the template template parameter can not be used in the body.
template <typename T, typename = void>
struct has_psl : std::false_type
{};
template <template <typename> typename EntryType, typename KeyType>
struct has_psl<EntryType<KeyType>, std::void_t<decltype(EntryType<KeyType>::psl)>> : std::true_type
{};
template <template <typename> typename EntryType, typename KeyType, std::enable_if<has_psl<EntryType<KeyType>>::value>>
bool f(EntryType<KeyType> *buffer, size_t offset, EntryType<KeyType> *new_entry) {
return true;
}
And then,
static_assert(has_psl<int>::value == false);
static_assert(has_psl<RawBytesEntry<int>>::value == true);
Online Demo
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;
}
};
How to detect whether there is a specific member variable in class?
Another way is this one, which relies on SFINAE for expressions too. If the name lookup results in ambiguity, the compiler will reject the template
template<typename T> struct HasX {
struct Fallback { int x; }; // introduce member name "x"
struct Derived : T, Fallback { };
template<typename C, C> struct ChT;
template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1];
template<typename C> static char (&f(...))[2];
static bool const value = sizeof(f<Derived>(0)) == 2;
};
struct A { int x; };
struct B { int X; };
int main() {
std::cout << HasX<A>::value << std::endl; // 1
std::cout << HasX<B>::value << std::endl; // 0
}
It's based on a brilliant idea of someone on usenet.
Note: HasX checks for any data or function member called x, with arbitrary type. The sole purpose of introducing the member name is to have a possible ambiguity for member-name lookup - the type of the member isn't important.
Templated check for the existence of a class member function?
Yes, with SFINAE you can check if a given class does provide a certain method. Here's the working code:
#include <iostream>
struct Hello
{
int helloworld() { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };
template <typename C> static one test( decltype(&C::helloworld) ) ;
template <typename C> static two test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}
I've just tested it with Linux and gcc 4.1/4.3. I don't know if it's portable to other platforms running different compilers.
SFINAE Based on Class Member Existence/Absence
why do we need an extra type (
special_
/general_
) in this method
These are used only for the purpose of allowing the modifyNormal
function to be overloaded with different implementations. What is special about them is that special_
uses the IS-A relationship since it inherits from general_
. Furthermore, the transform
function always calls the modifyNormal
overload which takes the special_
type, see the next part.
what does
typename int_<decltype(Lhs::normal)>::type = 0
help us do
This is a template argument with a default value. The default value is present so that the transform
function doesn't have to specify it, which is important because the other modifyNormal
function doesn't have this template parameter. Furthermore, this template parameter is only added to invoke SFINAE.
http://en.cppreference.com/w/cpp/language/sfinae
When substituting the deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.
So if a failure occurs the modifyNormal
function taking the special_
type is removed from the set of overloads to consider. This only leaves the modifyNormal
function taking a general_
type and since special_
IS-A general_
type everything still works.
If a substitution failure does not occur then the modifyNormal
function using the special_
type will be used since it's a better match.
Note: The general_
type is a struct
so the inheritance is public
by default, allowing the IS-A relationship without using the public
keyword.
Edit:
Can you comment on why we use the elaborate
typename int_<decltype(Lhs::normal)>::type
mechanism in the first place?
As stated above this is used to trigger the SFINAE behavior. However, it's not very elaborate when you break it down. At it's heart it wants to instantiate a instance of the int_
struct for some type T
and it has a type
data type defined:
int_<T>::type
Since this is being used in a template the typename
keyword needs to be added, see When is the “typename” keyword necessary?.
typename int_<T>::type
Lastly, what is the actual type used to instantiate the int_
struct? That's determined by decltype(Lhs::normal)
, which reports the type for Lhs::normal
. If type Lhs
type has a normal
data member then everything succeeds. However, if it doesn't then there is a substitution failure and the importance of this is explained above.
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.
Related Topics
C++11 Lambda Implementation and Memory Model
Very Poor Boost::Lexical_Cast Performance
What Is the Meaning and Usage of _Stdcall
Is There a Linq Library for C++
Are Parentheses Around the Result Significant in a Return Statement
Does the C++ Volatile Keyword Introduce a Memory Fence
Why Can't I Capture This By-Reference ('&This') in Lambda
Compile the Python Interpreter Statically
Nested Templates with Dependent Scope
What Is Uint_Fast32_T and Why Should It Be Used Instead of the Regular Int and Uint32_T
How Does the Standard Library Implement Std::Swap
How to Guarantee Order of Argument Evaluation When Calling a Function Object
Forward Declaration with Unique_Ptr
How to Avoid Precompiled Headers
How to Call MAChine Code Stored in Char Array
Spirit Qi Attribute Propagation Issue with Single-Member Struct