Partners: KDAB Whole Tomato Software CppDepend

25 February 2016

SFINAE Followup

SFINAE follow up

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

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

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:

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.

Get my free ebook about C++17!

More than 50 pages about the new Language Standard.

C++17 in detail, by Bartlomiej Filipek

© 2017, Bartlomiej Filipek, Blogger platform
Any opinions expressed herein are in no way representative of those of my employers.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.