0

I am trying to implement an own SmartPointer class and after an initial working version I have started to refine the code and now facing an issue, that I am unable to resolve.

Here is the first version:

template<class T>
class SmartPointer
{
private:
    T* ptr;
public:
    explicit SmartPointer(T* p = nullptr):ptr(p){}
    ~SmartPointer(){delete(ptr);}
    T& operator*(){return *ptr;}
    T* operator->(){return ptr;}
};

My problem with this that I have to call it specifying two times that this will be eg. an "int" type of pointer:

SmartPointer<int> intSP(new int());

So I have tried to create a template constructor inside the SmartPointer class's constructor, by changing it to:

template<typename... Args>
explicit SmartPointer(Args... args):ptr(new T(args...)){};

This works fine till the point I provide at least one parameter. But when no parameter is provided the whole class starts not to work at all. (when I have created an instance from it and tried to assign a value, it throw the following error: "assignment of read-only location '* intSP'".

So I have tried to complicate it further, with enable_if on the size of the argument pack, unfortunately with the same results as in the previous case. The enable_if seems not to do anything at all for some reason:

    template<typename... Args,
             typename = typename std::enable_if<(sizeof...(Args)>0u)>::type>
    explicit SmartPointer(Args... args):ptr(new T(args...)){
        cout << "constructor with arguments" << endl;
    };
    template<typename... Args,
             typename = typename std::enable_if<(sizeof...(Args)==0u)>::type>
    explicit SmartPointer():ptr(new T()){
        cout << "constructor without args" << endl;
    };

And finally the full code, to have some overview:

#include <iostream>

using namespace std;
#define var2str(var) #var
template<class T>
class SmartPointer
{
private:
    T* ptr;
public:
    template<typename... Args,
             typename = typename std::enable_if<(sizeof...(Args)>0u)>::type>
    explicit SmartPointer(Args... args):ptr(new T(args...)){
        cout << "constructor with arguments" << endl;
    };
    template<typename... Args,
             typename = typename std::enable_if<(sizeof...(Args)==0u)>::type>
    explicit SmartPointer():ptr(new T()){
        cout << "constructor without arguments" << endl;
    };
    ~SmartPointer(){delete(ptr);}
    T& operator*(){return *ptr;}
    T* operator->(){return ptr;}
};

int main(int, char**) {
    SmartPointer<int> intSP(5);//new int());

    cin>>*intSP;
    cout << *intSP << " stored in "<< var2str(intSP) << endl;
}
  • Do you know how to use [deduction guides in C++17](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)? – Sam Varshavchik Jan 04 '21 at 21:00
  • 2
    "But when no parameter is provided the whole class starts not to work at all" - Are you sure isn't a "most vexing parse problem"? I mean... have you written `SmartPointer intSP();` or `SmartPointer intSP{};`? – max66 Jan 04 '21 at 21:05
  • 1
    I read it thrice but could not get what the problem is... Please show [MCVE](https://stackoverflow.com/help/minimal-reproducible-example). – Evg Jan 04 '21 at 21:07
  • 1
    maybe a little off topic but wouldn´t be better to use: `explicit SmartPointer(Args &&... args) : ptr( new T( forward( args )... ) ) {}`? – Martin Kopecký Jan 04 '21 at 21:12
  • @max66 I have tried your proposal and can confirm `SmartPointer intSP{};` works while `SmartPointer intSP();` fails to compile. See https://coliru.stacked-crooked.com/a/77a5558146cb8afe – Martin Kopecký Jan 04 '21 at 21:24
  • 1
    Your constructor with `std::enable_if_t<(sizeof...(Args)==0u)>` is pedantically UB, as the only valid specialization is the empty pack. – Jarod42 Jan 04 '21 at 23:06

1 Answers1

1

I suppose your problem (with a single constructor) is caused from "vexing parse" (here a description of the "most vexing parse problem", a more spectacular version of the problem).

I mean... if you write

SmartPointer<int> intSP();

the compiler interpret it as a function declaration.

If you want initialize a variable without arguments you can use brackets

SmartPointer<int> intSP{};

or no parentheses

SmartPointer<int> intSP;

I suppose the following example should be useful

#include <iostream>

template <typename T>
class SmartPointer
 {
   private:
      T * ptr;

   public:
      template <typename... Args>
      explicit SmartPointer (Args && ... args)
         : ptr{ new T{ std::forward<Args>(args)... } }
       { }

      ~SmartPointer ()
       { delete(ptr); }

      T & operator* ()
       { return *ptr; }

      T * operator-> ()
       { return ptr; }
 };

int main ()
 {
   SmartPointer<int> intSP1; // OK
   //SmartPointer<int> intSP2(); // Error: vexing parse
   SmartPointer<int> intSP3{}; // OK
   SmartPointer<int> intSP4{5}; // OK
   SmartPointer<int> intSP5(5); // OK (no more vexing parse)
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • I think the *most vexing parse* is reserved to the more complex declaration, here it is not the **most** , but just a vexing parse... – Jarod42 Jan 04 '21 at 23:10
  • @Jarod42 - Ops! I was convinced that also the simpler form was defined "most vexing parse". Corrected. Thanks. – max66 Jan 05 '21 at 18:56
  • Thank you! This did the magic. And actually I have never heard about vexing parse. – Sándor Megyeri Jan 08 '21 at 14:53