2

I am trying to make the project for BinaryTree Operations, the following are the class prototype with the code in which I am having the problem.

BinaryTree Class in BinaryTree.h file

template <class T>
class BinaryTree
{
   public:  
       BinaryTree();
       BinaryTree(T);     
};

The constructor implementation in BinaryTree.cpp

template<class T>
struct isPointer
{
  static const bool value=false;
};

template<class T>
struct isPointer<T*>
{
  static const bool value=true;
};

template<class T>
BinaryTree<T>::BinaryTree():root(nullptr)
{
  //ctor
}

template<class T>
BinaryTree<T>::BinaryTree(T data)
{
  if(isPointer<T>::value == true)
  {
   if(data != nullptr)
   {
      //Do something
   }
  }      
}

BinaryTreeOperations Class inherit BinaryTree Class and its prototype is defined in BinaryTreeOperations.h

template<class T>
class BinaryTreeOperations:public BinaryTree<T>
{
  public:

  //Constructors
  BinaryTreeOperations();
  BinaryTreeOperations(T);
};

While the constructors are defined in BinaryTreeOperations.cpp class

template<class T>
BinaryTreeOperations<T>::BinaryTreeOperations(T data):BinaryTree<T>(data)
{
  //ctor
}

The main function in Main.cpp file

int main()
{
  cout << "Hello world!" << endl;

  BinaryTreeOperations<std::string> b("1");
}

And now the error which g++ is throwing is

no match for 'operator!=' (operand types are 'std::__cxx11::basic_string' and 'std::nullptr_t')

at line

if(data != nullptr)

in BinaryTree Constructor defined in BinaryTree.cpp class

Here comes the issue. I have already defined isPointer structure to check whether the given template is pointer or not. But it seems, in spite of T being std::string g++ is going in if(isPointer<T>::value == true) condition.

I don't understand what am I doing wrong? Any sort of guidance will be deeply appreciated.

Dr. Xperience
  • 475
  • 1
  • 5
  • 18
  • 7
    `if` is a strictly runtime check, both branches must compile correctly even if one of them is dead. – Quentin Nov 28 '16 at 13:31
  • @Bathsheba will using std::is_pointer give me a different result. – Dr. Xperience Nov 28 '16 at 13:34
  • @Quentin hmm fair point any work around to this problem. – Dr. Xperience Nov 28 '16 at 13:34
  • 2
    Yes: two overloads, one for pointers and one for non-pointers, dispatched using [`std::enable_if`](http://en.cppreference.com/w/cpp/types/enable_if). – Quentin Nov 28 '16 at 13:36
  • 1
    Possible duplicate of [how to do an if else depending type of type in c++ template?](http://stackoverflow.com/questions/15199833/how-to-do-an-if-else-depending-type-of-type-in-c-template). Consider using overloading with [`std::enable_if`](http://en.cppreference.com/w/cpp/types/enable_if), as @Quentin suggested – rocambille Nov 28 '16 at 13:36
  • 1
    You should also take a look at [Why can templates only be implemented in the header file?](http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file/495056#495056) – rocambille Nov 28 '16 at 13:38
  • http://en.cppreference.com/w/cpp/types/is_pointer – Simon Kraemer Nov 28 '16 at 13:39
  • @Wasthishelpful I don't see how my case is linked to that. I am making a thoroughly generalised case. While the other question is asking to match with some cases. – Dr. Xperience Nov 28 '16 at 13:46
  • @Quentin I will try overload. But I am not familiar with enable_if. Will try that and respond. If possible could you give a sample code in the answer? I will mark it as an answer and will end this problem. – Dr. Xperience Nov 28 '16 at 13:52

3 Answers3

2
  if(isPointer<T>::value == true)
  {
   if(data != nullptr)
   {
      //Do something
   }
  }      

This is going to get compiled for every T. Even though for a given T the expression isPointer<T>::value will evaluate to false, this whole thing is going to get compiled. And if data, an instance of T, cannot be compared to nullptr, this results in a compilation failure.

C++17 introduces static_if, a compile-time directive that selectively compiles chunks of code based on a compile-time constexpr (in itself, a mildly controversial addition). Before C++17, the only way to do this correctly is by using specialization to selectively compile this chunk of code too, in addition to the specialization for isPointer.

But, in any case, the answer to your compilation error is that it is equivalent to:

std::string data;

if (0)
{
   if (data != nullptr)
   {
      // ...
   }
}

The fact that the code in question will never be reached does not change the fact that it must be valid, compilable, C++ code.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • if(0) mean false. Don't you mean if(1). And any suggestion for this terrible terrible mistake. – Dr. Xperience Nov 28 '16 at 13:40
  • No, I specifically meant `if (0)`, the equivalent situation in the question that results in a compilation error, which occurs when `T` is a `std::string`. – Sam Varshavchik Nov 28 '16 at 13:40
  • Also, I wouldn't call this, exactly, a "terrible" mistake. A fairly common, arcane aspect of template instantiation that's often misunderstood. – Sam Varshavchik Nov 28 '16 at 13:41
2

This is a case for which constexpr if (C++17) would have been a nice addition: your branches are going to be evaluated at compile-time for every T you pass.

A possible workaround is to leverage enable_if and define two constructor versions depending on the result of your isPointer predicate and let SFINAE do its job

template <class T>
class BinaryTree
{
public:
  BinaryTree();

  template<typename TT = T>
  BinaryTree(TT, typename std::enable_if<!isPointer<TT>::value>::type * = nullptr) {

  }

  template<typename TT = T>
  BinaryTree(TT data, typename std::enable_if<isPointer<TT>::value>::type * = nullptr) {
    if (data != nullptr)
    {
      //Do something (this is a pointer)
    }
  }

};

Example

or alternatively refactor your code keeping in mind the fact that a template is exactly a template and when instantiated it's going to generate code for its arguments on any code branch it defines.

Marco A.
  • 43,032
  • 26
  • 132
  • 246
  • Thanks bro for giving a solution using enable_if. I was not familiar with enable_if concept. Now I wish to mark two solutions as answer for my question. – Dr. Xperience Nov 28 '16 at 13:59
  • I facing a weird issue when using this on function accepting a string literal. `template::value == true>::type * = nullptr> void test(TT data) { std::cout<<" TT isPointer true"<::value==false) std::cout<<" T isPointer false"< – Dr. Xperience Jan 18 '17 at 10:46
  • 1
    Oh I got it I should have made `void test(T data)` not `void test(TT data)` my mistake. – Dr. Xperience Jan 18 '17 at 10:54
1

But it seems, in spite of T being std::string g++ is going in if(isPointer<T>::value == true) condition.

Indeed. The entire function will be compiled. Whether the execution can ever reach that comparison is irrelevant.

A Solution: Write a function template and overload it for pointers.

template<class T>
void do_something(const T&) {
    // do nothing
}

template<class T>
void do_something(T* data) {
    if(data != nullptr) {
        // do something
   }
}

//  ...

template<class T>
BinaryTree<T>::BinaryTree(T data)
{
  do_something(data);
}

C++17 is planned to introduce constexpr if, which would allow you to write a compile time conditional in-place within a single function.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thanks. Mr.@Quentin said something about dispatched using std::enable_if in the comments of the question. I have never used enable_if could you guide me a little with same. – Dr. Xperience Nov 28 '16 at 13:55
  • 1
    @Dr.Xperience you don't necessarily need `enable_if` and in this case it will only make this program slightly more verbose. However, it can be useful to avoid repetition of multiple specializations in case you did were to use a more complicated trait condition that your `isPointer`. I recommend that you take a look at existing answers on stackoverflow for how to use it. – eerorika Nov 28 '16 at 14:00