Question

The book C++ Programming Language(fourth edition). Chapter 28.4 (page 796) explains enable_if and gives an example of making the definition of operator->() conditional. The example in the book is only a code snippet, and I completed it to a program as follows:

#include <iostream>
#include <type_traits>
#include <complex>
using namespace std;
template<bool B, typename T>
using Enable_if=typename std::enable_if<B,T>::type;

template<typename T>
constexpr bool Is_class(){  //the book example misses constexpr
  return std::is_class<T>::value;
}

template<typename T>
class Smart_pointer{
public:
  Smart_pointer(T* p):data(p){
  }
  T& operator*();
  Enable_if<Is_class<T>(),T>* operator->(){
    return data;
  }
  ~Smart_pointer(){
    delete data;
  }
private:
  T* data;  
};
int main()
{
  Smart_pointer<double> p(new double);  //compiling error in g++ 4.7.2: no type named 'type' in 'struct std::enable_if<false, double>'
  //Smart_pointer<std::complex<double>> q(new std::complex<double>);//compile successfully.
}  

The code above doesn't compile in gcc 4.7.2. The compiler complains: error: no type named 'type' in 'struct std::enable_if'

According to the explanation in the book, the operator->() will be ignored if T is not a class. However, that doesn't explain the compiling error. The compiling error indicates the opposite, the definition of operator->() is not ignored even if T is not a class. The compiling error seems to be explained by this post std::enable_if to conditionally compile a member function . But the post seems inconsistent with the book explanation. Anyone can help and explain the usage of the enable_if for member function? In the above example, if I do want to define the operator->() only when T is class, is there a clean and elegant solution? Thank you.

Thank you very much for the replies. There is another workaround based on overloading(I wouldn't say it's better than other posted solutions)

template<typename T>
T* Smart_pointer<T>::operator->(){
  op_arrow(std::integral_constant<bool,Is_class<T>()>());
}
private: 
T* op_arrow(std::true_type){
return data; 
}
T* op_arrow(std::false_type);
Was it helpful?

Solution

first of all, as mentioned by @jrok and the post you linked to: you have to have a template function to use enable_if. but it is not a good idea in your particular case, because there is no reason to do this operator-> templated. moreover, it wouldn't work! cuz there is no way to instantiate a particular operator->()! it doesn't have (and can't) any parameters and there is no syntax to specify it when you call it on some object! so, T (or whateve dummy type) will/could not be deduced from this call.

so, as a workaround you may use compile-time conditional inheritance. i.e. smth like this:

template <typename T, bool IsClass>
struct smart_base
{
    struct base {};
};

template <typename T>
struct smart_base<T, true>
{
    struct base 
    {
        T* operator->()
        {
            // do that ever you wanted to do
        }
    };
};

template <typename T>
struct smart : public smart_base<T, std::is_class<T>::value>::base
{
    // depending on T here you have, or have no operator-> inherited
};

you have to understand, that having operator-> actually in your base class will require to move some function and/or data members to base-of-the-base class :) or you may use CRTP technique to access members of a derived class from the base one :)

OTHER TIPS

You can constrain operator-> in C++11, but it requires a fairly awkward trick. You have to make the member a template, and you have to make the Enable_if dependent on a template parameter so that SFINAE happens when the member is instantiated instead of a simple error when the class is instantiated (Live at Coliru):

template<typename T>
class Smart_pointer {
public:
  ...
  template <class U=T,class=Enable_if<Is_class<U>(),void>>
  T* operator->() const { return data; }
};

That said, I don't know if it's really a good idea to do so. The error message when used with an inappropriate T:

main.cpp:35:4: error: base operand of ‘->’ has non-pointer type ‘Smart_pointer<double>’
   p->foo();
    ^

isn't really that much improved over what you get if you just declare operator-> unconditionally:

template<typename T>
class Smart_pointer {
public:
  ...
  T* operator->() const { return data; }
};

main.cpp:35:6: error: request for member ‘foo’ in ‘* p.Smart_pointer<T>::operator-><double>()’, which is of non-class type ‘double’
   p->foo();
      ^

and everyone who ever reads your code won't have to ask you to explain what the hell is going on with operator->.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top