2

Consider the following class:

template<typename T> class A{
public:
    virtual void foo(const T& v){ m_v = v + 1; }    
    T& bar(){ return m_v;}
    T m_v;
    // ... other member functions/variables ...
};

How can it be modified such that the following code works (without, if possible having to to a const_cast on p):

int main(){
    A<int*> a;
    const int* p = nullptr;
    a.foo(p);
    a.bar();

    A<int>().foo(10);
}

The problem is that the signature of the foo method, when A is templated by int* becomes, as far as I can tell, foo(int* const&). What we would like to have would be something like foo(const int* const&).

Until now, I considered adding a specialized implementation of the foo member function for A templated by pointer types outside the class declaration (as one would do for specializing A<int>::foo), but the compiler is not able to resolve the function definition prototype to the declared method.

template<typename T>
void A<T*>::foo(const T* const& ){}

gcc complains that it's an invalid use of incomplete type ‘class A<T*>’

I've also considered adding a partial specialization for the whole class for pointer types which defines an extra member overloading foo that takes in a const T*, but I couldn't figure out how to reuse the rest of the code in the base template (i.e. without duplicating the declarations and definitions for all the other functions, e.g. bar). Is there a way to reference the base template class from the pointer partial specialization, either for inheritance or for having a member to which to forward calls? (This post provides a solution by adding an extra dummy template argument. Is there a way to circumvent this?).

Finally, I've also thought about using enable_if<is_pointer<T>::value, void>::type foo(const PointedToType*); to add an extra overload to the foo method when the template parameter is a pointer, but how can one get PointedToType from T (when it is known that T is a pointer type)?

Barry
  • 286,269
  • 29
  • 621
  • 977
bbudescu
  • 313
  • 1
  • 3
  • 9
  • It's not clear to me what you need. Getting the call accepted could be as easy as turning `foo` into a template itself, i.e., `template void foo(const U&){}` and adding some constrains with `static_assert` to the function's body - but I think it depends on the implementation of the *real* function whether or not that makes sense. – Daniel Frey Jul 14 '15 at 22:14
  • Unfortunately, the _real_ `A` is an interface which must be inherited from and C++ doesn't allow virtual template member functions. – bbudescu Jul 14 '15 at 22:39
  • Did you try `A a:`? – Matthias J. Sax Jul 14 '15 at 22:46
  • `A` has a member variable of type `T` which must be non-const. Sorry for not specifying this in the question from the beginning. – bbudescu Jul 14 '15 at 22:51
  • @bbudescu Please add the info that `foo` is `virtual` to your question. It made my answer useless. – 5gon12eder Jul 14 '15 at 22:51
  • @5gon12eder Sorry about that. Actually, your answer helped. I wasn't aware of `std::remove_pointer`. Declaring foo as `typename enable_if::value, void>::type foo(const typename remove_pointer::type *)` also does the trick. – bbudescu Jul 14 '15 at 23:05
  • I don't see how this can help here but if it does, I'll undelete my answer again. – 5gon12eder Jul 14 '15 at 23:08

3 Answers3

3

Seems to me that you just need a trait for what the argument type of foo should be:

template <typename T>
struct arg_type { using type = T const&; };

template <typename T>
struct arg_type<T*> { using type = T const*; };

template <typename T>
using arg_type_t = typename arg_type<T>::type;

To be used thusly:

virtual void foo(arg_type_t<T> arg ) {}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • This does the trick quite nicely. However, do you think there would be a way to make the interface more legible (i.e. for an interface user to easily figure out what the method expects as a parameter)? – bbudescu Jul 15 '15 at 07:53
  • @bbudescu Add a comment? – Barry Jul 15 '15 at 11:26
  • I mean, if a user who is not very familiar with type traits sees the declaration `virtual void foo(arg_type_t arg `, maybe he wouldn't be able to figure out that the function expects a `const T&` or `const T*` right away. I was just wondering if there's a way to rewrite the code such that it is easier to deduce how to use it. Of course, comments would be very helpful in this situation. I'm not very familiar with type traits, and I was wondering whether there is a way to make the declaration more legible. – bbudescu Jul 15 '15 at 16:03
  • I added an edit to the question with what I ended up doing. – bbudescu Jul 15 '15 at 21:05
  • @bbudescu That's irrelevant to the question. – Barry Jul 15 '15 at 21:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/83371/discussion-between-bbudescu-and-barry). – bbudescu Jul 15 '15 at 21:14
1

I'm not sure if this is what you want but you could use a combination of std::is_pointer, std::remove_pointer and std::conditional to construct the argument type you want. I don't know how you would write the function body to do useful things with that argument, however.

#include <type_traits>

template <typename T>
struct A
{
  template <
    typename U = T,
    typename ArgT = std::conditional_t<
      std::is_pointer<U>::value,
      const std::remove_pointer_t<U> *,
      U>
  >
  void
  foo(const ArgT&)
  {
    // ...
  }
};

I'm using the C++14 type meta-functions here. If you cannot use C++14, replace std::fancy_t< … > with typename std::fancy< … >::type.

The following is now valid:

int
main()
{
  {
    A<int *> a {};
    const int * p {};
    a.foo(p);
  }
  {
    A<int> a {};
    const int i {};
    a.foo(i);
  }
}
5gon12eder
  • 24,280
  • 5
  • 45
  • 92
0

You could just define a with a const template argument, i.e., A<const int*> a;. That would work.

template<typename T> class A{
public:
    void foo(const T &v){ m = v; }
    void bar(T &v){ v = m; }
    T m;
    // ... other member functions/variables ...
};

int main(){
    A<const int*> a;
    const int* p = nullptr;
    a.foo(p);
    a.bar(p);
}
plong
  • 1,723
  • 3
  • 14
  • 14
  • Sorry I didn't specify this in the question from the beginning (I added a later edit to illustrate this). `A` has other members of type `T` which must be accessible by non-const references. – bbudescu Jul 14 '15 at 23:20
  • @bbudescu That still works. Notice the `bar()` member. It is expecting a non-`const` reference. – plong Jul 14 '15 at 23:32
  • The functions behave like a setter / getter pair. The interface for the setter _should_ accept a `const` parameter, even if the data is not `const`. – bbudescu Jul 15 '15 at 07:39