Note the decltype statement below contains 2 elements: reserve and bool: decltype(t.reserve(0), bool())
This is a trick using SFINAE and the comma operator: SFINAE will cull the function if 'reserve' doesn't exist and the comma operator will mean the result type of the decltype statement will be a bool.
This means we can easily implement an 'enable_if'esque statement to check for the existence of a member function called 'reserve'
// 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; }
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) {}
};
template <typename T>
bool reserve(T& t, size_t n)
{
Reserver<T, has_reserve_method(t)>::apply(t, n);
return has_reserve_method(t);
}
(Thanks to Matthieu M for his post on stackoverflow here)
--------------------------
Another implementation which has 2 SFINAE functions to access a member int, items_n or items_c, ultimately falling back to 0 if neither exist
// culled by SFINAE if items_n does not exist
template<typename T>
constexpr auto has_items_n(int) -> decltype(std::declval<T>().items_n, bool())
{
return true;
}
// catch-all fallback for items with no items_n
template<typename T> constexpr bool has_items_n(...)
{
return false;
}
//-----------------------------------------------------
template<typename T, bool>
struct GetItemsN
{
static int value(T& t)
{
return t.items_n;
}
};
template<typename T>
struct GetItemsN<T, false>
{
static int value(T&)
{
return 0;
}
};
//-----------------------------------------------------
// culled by SFINAE if items_c does not exist
template<typename T>
constexpr auto has_items_c(int) -> decltype(std::declval<T>().items_c, bool())
{
return true;
}
// catch-all fallback for items with no items_c
template<typename T> constexpr bool has_items_c(...)
{
return false;
}
//-----------------------------------------------------
template<typename T, bool>
struct GetItemsC
{
static int value(T& t)
{
return t.items_c;
}
};
template<typename T>
struct GetItemsC<T, false>
{
static int value(T&)
{
return 0;
}
};
//-----------------------------------------------------
template<typename T>
int get_items(T& t)
{
if (has_items_n<T>(0))
return GetItemsN<T, has_items_n<T>(0)>::value(t);
if (has_items_c<T>(0))
return GetItemsC<T, has_items_c<T>(0)>::value(t);
return 0;
}
//-----------------------------------------------------
When you have two candidates function templates, and want to use SFINAE to choose between them, sometimes you may have a parameter for which both overloads will work.
To prevent ambiguity you can favour one overload over the other.
By using implicit type casting we can make one overload a better match, therefore resolving the ambiguity.
#include <iostream>
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
-> decltype(os << obj, void())
{
os << obj;
}
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
-> decltype(obj.stream(os), void())
{
obj.stream(os);
}
template<class T>
auto serialize(std::ostream& os, T const& obj)
-> decltype(serialize_imp(os, obj, 0), void())
{
serialize_imp(os, obj, 0);
}
struct X
{
void stream(std::ostream&) const
{
std::cout << "\nX::stream()\n";
}
};
int main(){
serialize(std::cout, 5);
X x;
serialize(std::cout, x);
}
--------------------------
Another implementation which has 2 SFINAE functions to access a member int, items_n or items_c, ultimately falling back to 0 if neither exist
// culled by SFINAE if items_n does not exist
template<typename T>
constexpr auto has_items_n(int) -> decltype(std::declval<T>().items_n, bool())
{
return true;
}
// catch-all fallback for items with no items_n
template<typename T> constexpr bool has_items_n(...)
{
return false;
}
//-----------------------------------------------------
template<typename T, bool>
struct GetItemsN
{
static int value(T& t)
{
return t.items_n;
}
};
template<typename T>
struct GetItemsN<T, false>
{
static int value(T&)
{
return 0;
}
};
//-----------------------------------------------------
// culled by SFINAE if items_c does not exist
template<typename T>
constexpr auto has_items_c(int) -> decltype(std::declval<T>().items_c, bool())
{
return true;
}
// catch-all fallback for items with no items_c
template<typename T> constexpr bool has_items_c(...)
{
return false;
}
//-----------------------------------------------------
template<typename T, bool>
struct GetItemsC
{
static int value(T& t)
{
return t.items_c;
}
};
template<typename T>
struct GetItemsC<T, false>
{
static int value(T&)
{
return 0;
}
};
//-----------------------------------------------------
template<typename T>
int get_items(T& t)
{
if (has_items_n<T>(0))
return GetItemsN<T, has_items_n<T>(0)>::value(t);
if (has_items_c<T>(0))
return GetItemsC<T, has_items_c<T>(0)>::value(t);
return 0;
}
//-----------------------------------------------------
When you have two candidates function templates, and want to use SFINAE to choose between them, sometimes you may have a parameter for which both overloads will work.
To prevent ambiguity you can favour one overload over the other.
By using implicit type casting we can make one overload a better match, therefore resolving the ambiguity.
#include <iostream>
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
-> decltype(os << obj, void())
{
os << obj;
}
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
-> decltype(obj.stream(os), void())
{
obj.stream(os);
}
template<class T>
auto serialize(std::ostream& os, T const& obj)
-> decltype(serialize_imp(os, obj, 0), void())
{
serialize_imp(os, obj, 0);
}
struct X
{
void stream(std::ostream&) const
{
std::cout << "\nX::stream()\n";
}
};
int main(){
serialize(std::cout, 5);
X x;
serialize(std::cout, x);
}
Here the ostream operator overload will be chosen when an object with both operator<< and stream() because by passing in 0 for the 3rd parameter of serialize_imp, we choose the overload with the int parameter, as 0 is an int, whereas the long parameter would require an implicit cast.
(Thanks to Xeo for his post on stackoverflow here)