2

I have the following class

template<typename T>
class A
{
public:
   A(T* d)  : ptr(d)
   {}
   A(const T* d) : ptr(const_cast<T*>(d))
   {}

   T* Ptr()
   {
       static_assert(???, "Not allowed when using A(const T* d)");
       return ptr;
   }
   const T* Ptr() const
   {
       return ptr;
   }

private:
   T* ptr;
}

How can I achieve that when Ptr() is compiled, I know which constructor was used to create this object? I want to statically assert that when Ptr() is compiled that the consstructor A(T* d) was used:

unsigned char* ptr = new unsigned char[10];
const unsigned char* cptr = new unsigned char[10];

A a(ptr);
A ca(cptr);

a.Ptr(); // Compiles
ca.Ptr(); // Gives compile error

I want to detect compile time if the programmer calls Ptr() when the object of class A was created using a const ptr. Calling Foo when created with const ptr is not allowed

I want to use it like this

void Foo(A<int>& r)
{
 ....
 int* ptr = a.Ptr();
 ....
}

void Bar(const A<int>& r)
{
  ...
}

...

A a(ptr);
A ca(cptr);

Bar(a);
Bar(ac);
Foo(a);
Foo(ac);// Gives compile error
thorsan
  • 1,034
  • 8
  • 19
  • You want to *indicate* it with a `static_assert`?? – WhiZTiM Sep 19 '17 at 09:48
  • not clear what you mean. If you compile the code you show here no constructor will be called, strictly speaking you dont even define a class (this is just a template) – 463035818_is_not_an_ai Sep 19 '17 at 09:49
  • 1
    perhaps you should explain why you think you need to know what constructor was called. Using a `static_assert` isnt a solution – 463035818_is_not_an_ai Sep 19 '17 at 09:51
  • @tobi303 I want to detect compile time if the programmer calls Foo() when the object of class A was created using a const ptr. Calling Foo when created with const ptr is not allowed – thorsan Sep 19 '17 at 09:55
  • 1
    If you *really* need that information, the constructors could of course save info in a member `is_const = false` and `is_const = true`. The traditional way to get a constant object would otherwise be `const A ca(cptr);`. – Bo Persson Sep 19 '17 at 09:56
  • 2
    Did you try making the second constructor `private`? – ypnos Sep 19 '17 at 09:57
  • 4
    @ypnos better yet, `= delete` it, see e.g. https://stackoverflow.com/questions/5702100/whats-the-most-reliable-way-to-prohibit-a-copy-constructor-in-c – sehe Sep 19 '17 at 09:57
  • If you want to allow a function call for one object but not for another, these objects must be of different types. – n. m. could be an AI Sep 19 '17 at 10:20
  • @n.m. strictly speaking yes but I this can be done with additional template parameter and deduction guides which will provide the different type of object according to constructor parameters (types)... (of course in c++17 if it's in the game) – W.F. Sep 19 '17 at 10:27
  • The solution for a broken API is to fix the API, not to add another dimension of hacks to it. You're solving the wrong problem here. – You Sep 19 '17 at 10:57
  • I cannot agree. From what @You are saying the API is broken if one want to forbid to use a member function for a given template parameter. In this case you should also forbid using template specialization not mentioning function overloads... – W.F. Sep 19 '17 at 11:08
  • 1
    @W.F.: What I mean is that the problem as posed just looks like the class has poor const correctness, and OP is trying to work around that by forbidding calling certain member functions. Arguably your solution "fixes" the API in some sense, but the fundamental problem (casting away the constness before storing the pointer) still persists. – You Sep 19 '17 at 11:13
  • Yes in this sense @You are right - using `const_cast` just like any other UB-prone casts should always be avoided. OP should rethink his design in this matter. – W.F. Sep 19 '17 at 11:19

2 Answers2

3

The simplest c++17 (as I can tell you are using it anyway to deduce template argument type) approach is to use user defined deduction guides and additional tagging non-type template parameter:

enum Tag {                       // 
    NonConstTag,                 //   Additional tag enum
    ConstTag                     //
};                               //

template<typename T, Tag TT>
//                   ^^^^^^
// Additional non-type template parameter to provide
// different type of A in case of calling const parameter 
// constructor
class A
{
public:
   A(T* d)  : ptr(d)
   {}
   A(const T* d) : ptr(const_cast<T*>(d))
   {}

   T* Ptr()
   {
       static_assert(TT == NonConstTag, "Not allowed when using A(const T* d)");
       return ptr;
   }
   const T* Ptr() const
   {
       return ptr;
   }
private:
   T* ptr;
};

template<typename T>             //
A(T* d) -> A<T, NonConstTag>;    //
                                 //    Deduction guides
template<typename T>             //
A(const T* d) -> A<T, ConstTag>; //

int main() {
    unsigned char* ptr = new unsigned char[10];
    const unsigned char* cptr = new unsigned char[10];

    A a(ptr);
    A ca(cptr);

    a.Ptr(); // Compiles
    //ca.Ptr(); // Gives compile error
}

[live demo]


Edit:

A little improvement to satisfy const correctness:

enum Tag {
    NonConstTag,
    ConstTag
};

template<typename T, Tag TT>
class A
{
public:
   A(T* d)  : ptr(d), cptr(d)
   {}
   A(const T* d) : ptr(nullptr), cptr(d)
   {}

   T* Ptr()
   {
       static_assert(TT == NonConstTag, "Not allowed when using A(const T* d)");
       return ptr;
   }
   const T* Ptr() const
   {
       return cptr;
   }
private:
   T* ptr;
   const T* cptr;
};

template<typename T>
A(T* d) -> A<T, NonConstTag>;

template<typename T>
A(const T* d) -> A<T, ConstTag>;

int main() {
    unsigned char* ptr = new unsigned char[10];
    const unsigned char* cptr = new unsigned char[10];

    A a(ptr);
    A ca(cptr);

    a.Ptr(); // Compiles
    //ca.Ptr(); // Gives compile error
}

[live demo]

W.F.
  • 13,888
  • 2
  • 34
  • 81
0

If you have an IDE it may allow you to jump to the declaration that corresponds to the call. (YouCompleteMe does this in Vim with :YcmCompleter GoTo; I believe Visual Studio has F12 or Alt-F12, if I remember correctly).

Besides that, if you want to detect it runtime, set a flag:

template <typename T> class A {
  public:
    A(T *) {}
    A(const T *) : used_const_arg_ctor(true) {}

  private:
    bool used_const_arg_ctor = false;
    void Foo() {
        if (used_const_arg_ctor) {
        }
    }
};

To actually static assert it, make it a constexpr type:

#include <boost/asio.hpp>

template <typename T> class A {
  public:
    constexpr A(T *) {}
    constexpr A(const T *) : used_const_arg_ctor(true) {}

    constexpr bool Foo() const {
        return used_const_arg_ctor;
    }
  private:
    bool used_const_arg_ctor = false;
};

int main() {
    int const i = 42;
    constexpr A<int> a(&i);

    static_assert(a.Foo(), "is constructed from const pointer");
}

This has limited usefulness. I'd suggest, instead, to have T reflect constness:

template <typename T> class A {
  public:
    A(T*d) : _v(d) {}

    constexpr bool IsConst() const { return std::is_const<T>::value; }
  private:
    T* _v;
};
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I agree on the last solution, but I want "const A" to reflect constness – thorsan Sep 19 '17 at 10:01
  • What's stopping you. You can. http://en.cppreference.com/w/cpp/language/member_functions#const-.2C_volatile-.2C_and_ref-qualified_member_functions – sehe Sep 19 '17 at 10:04