Last Update:
SFINAE Followup
Table of Contents
As it appears, my last post about SFINAE wasn’t that bad! I got a valuable comments and suggestions from many people. This post gathers that feedback.
Comments from @reddit/cpp
Using modern approach
In one comment, STL (Stephan T. Lavavej) mentioned that the solution I presented in the article was from old Cpp style. What is this new and modern style then?
decltype
decltype
is a
powerful tool that returns type of a given expression. We already use it
for:
template <typename C>
static YesType& test( decltype(&C::ToString) ) ;
It returns the type of C::ToString
member method (if such method
exists in the context of that class).
declval
declval
is utility
that lets you call a method on a T without creating a real object. In
our case we might use it to check return type of a method:
decltype(declval<T>().toString())
constexpr
constexpr
suggests the compiler to evaluate expressions at compile time (if
possible). Without that our checker methods might be only evaluated at
run time. So the new style suggest adding constexpr
for most of
methods.
Akrzemi1: “constexpr” function is not “const”
void_t
- SO question: Using
void_t
to check if a class has a method with a specific signature - SO question: How does
void_t
work
Full video for the lecture:
Starting at around 29 minute, and especially around 39 minute.
This is amazing meta-programming pattern! I don’t want to spoil anything, so just watch the video and you should understand the idea! :)
detection idiom
- WG21 N4436, PDF - Proposing Standard Library Support for the C++ Detection Idiom, by Walter E. Brown
- std::is_detected
- wikibooks: C++ Member Detector
Walter E. Brown proposes a whole utility class that can be used for
checking interfaces and other properties of a given class. Of course,
most of it is based on void_t
technique.
Check for return type
Last time I’ve given an open question how to check for the return type
of the ToString()
method. My original code could detect if there is a
method of given name, but it wasn’t checking for the return type.
Björn Fahller has given me the following answer: (in the comment below the article)
template <typename T>
class has_string{
template <typename U>
static constexpr std::false_type test(...) { return {};}
template <typename U>
static constexpr auto test(U* u) ->
typename std::is_same<std::string, decltype(u->to_string())>::type { return {}; }
public:
static constexpr bool value = test<T>(nullptr);
};
class with_string {
public:
std::string to_string();
};
class wrong_string{
public:
const char* to_string();
};
int main() {
std::cout
<< has_string<int>::value
<< has_string<with_string>::value
<< has_string<wrong_string>::value << '\n';
}
It will print:
010
In the test
method we check if the return type of to_string()
is the
same as the desired one: std::string()
. This class contains two levels
of testing: one with SFINAE - a test if there is to_string
in a given
class (if not we fall-back to test(...)
). Then, we check if the return
type is what we want. At the end we’ll get has_string<T>::value
equals
to false
when we pass a wrong class or a class with wrong return type
for to_string
. A very nice example!
Please notice that constexpr
are placed before the ::value
and
test()
methods, so we’re using a definitely more modern approach here.
More Examples
Pointers conversion:
@fenbf I found that useful for static cast of smart pointers by pointee-type, template def is ugly but does the job: https://t.co/tH5dAgOrYT
— Andre Weissflog (@FlohOfWoe) February 18, 2016
Let’s look at the code:
/// cast to compatible type
template<class U,
class=typename std::enable_if<std::is_convertible<T*,U*>::value>::type>
operator const Ptr<U>&() const
{
return *(const Ptr<U>*)this;
};
This is a part of Ptr.h - smart pointer class file, from oryol - Experimental C++11 multi-platform 3D engine
It’s probably hard to read, but let’s try:
The core thing is std::is_convertible<T*,U*>
(see
std::is_convertible
reference). It’s
wrapped into enable_if
. Basically, when the two pointers can be
converted then we’ll get a valid function overload. Otherwise compiler
will complain.
Got more examples? Let me know! :)
Updated version
If I am correct and assuming you have void_t
in your compiler/library,
this is a new version of the code:
// default template:
template< class , class = void >
struct has_toString : false_type { };
// specialized as has_member< T , void > or sfinae
template< class T >
struct has_toString< T , void_t<decltype(&T::toString) > > : std::is_same<std::string, decltype(declval<T>().toString())>
{ };
http://melpon.org/wandbox/permlink/ZzSz25GJVaY4cvzw
Pretty nice… right? :)
It uses explicit detection idiom based on void_t
. Basically, when
there is no T::toString()
in the class, SFINAE happens and we end up
with the general, default template (and thus with false_type
). But
when there is such method in the class, the specialized version of the
template is chosen. This could be the end if we don’t care about the
return type of the method. But in this version we check this by
inheriting from std::is_same
. The code checks if the return type of
the method is std::string
. Then we can end up with true_type
or
false_type
.
Summary
Once again thanks for your feedback. After the publication I got convinced that SFINAE/templates are even more confusing and I know nothing about them :) Still it’s worth trying to understand the mechanisms behind.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: