C++ passing a const pointer by const reference

12,593

Solution 1

It looks like your issue here as that as soon as you have a pointer type mapped to a template type, you can no longer add const-ness to the pointed-to type, only to the pointer itself. What it looks like you're trying to do is automatically add constness to the parameter of your function (so if T is char* the function should accept const char* const& rather than char* const& as you've written). The only way to do that is with another template to add constness to the pointee for pointer types, as follows. I took the liberty of including missing headers and correcting the signature of main:

#include <cstdio>

template<typename T>
struct add_const_to_pointee
{
    typedef T type;
};

template <typename T>
struct add_const_to_pointee<T*>
{
    typedef const T* type;
};

template<typename T>
struct Foo
{
    void Add( typename add_const_to_pointee<T>::type const & Bar ) { printf(Bar); }
};

int main()
{
    Foo<char*> foo;
    const char* name = "FooBar";
    foo.Add(name);                 // Causes error
}

As mentioned in another another however, this issue just goes away if you use std::string instead of C-style strings.

Solution 2

There is a strong difference between a pointer to a constant object (T const*, or const T*) and a constant pointer to a non-constant object (T * const). In your case the signature of the member Add is:

void Foo<char *>::Add(char * const& ); // reference to a constant pointer to a 
                                       // non-constant char

I usually recommend that people drop the use of const on the left hand side exactly for this reason, as beginners usually confuse typedefs (or deduced types) with type substitution and when they read:

const T& [T == char*]

They misinterpret

const char*&

If the const is placed in the right place:

T const &

Things are simpler for beginners, as plain mental substitution works:

char * const &

A different problem than what you are asking, but maybe what you think you want, is:

Given a type T have a function that takes a U that is const T if T is not a pointer type, or X const * if T is a pointer to X

template <typename T>
struct add_const_here_or_there {
    typedef T const type;
};
template <typename T>
struct add_const_here_or_there<T*> {
    typedef T const * type;
};

Then you can use this in your signature:

template <typename T>
void Foo<T>::Add( const typename add_const_here_or_there<T>::type & arg ) {
     ...

Note that I am adding two const in the signature, so in your case char* will map to char const * const &, as it seems that you want to pass a const& to something and you also want the pointed type to be const.

You might have wondered as of the name for the metafunction: *add_const_here_or_there*, it is like that for a reason: there is no simple way of describing what you are trying to do, which is usually a code smell. But here you have your solution.

Solution 3

You need to change the template argument to your Foo object to Foo<const char*>. Because if T=char*, then const T=char*const, not const char*. Trying to coerce it to work is not a good idea and would probably result in undefined behavior.

Solution 4

Use:

Foo<const char*> foo;
const char* name = "FooBar"; 
foo.Add(name);     

And write int main() instead of void main()

Solution 5

If passing const char* instead of char* to Foo is not an option you can finesse the correct type with std::remove_pointer. This will remove the pointer modifier and allow you to provide a more explicit type.

#include <type_traits>

template<typename T>
struct Foo
{
    void Add(typename std::remove_pointer<T>::type const*& Bar ) { printf(Bar); }
};

To prevent the pointer value from being modified you can declare the reference as const as well.

void Add(typename std::remove_pointer<T>::type const* const& Bar )
{ Bar = "name"; } // <- fails

If you need to reduce the type from say a pointer to pointer you can use std::decay along with std::remove_pointer

void Add(typename std::remove_pointer<typename std::decay<T>::type>::type const*& Bar)
{
    printf(Bar);
}

This really depends on what your requirements for T are. I suggest assuming only the base type (e.g. char) is passed as T and building reference and pointer types from that.

Share:
12,593
user176168
Author by

user176168

Updated on June 08, 2022

Comments

  • user176168
    user176168 about 2 years

    Hmm a strange one in VC2012 I can't seem to work out the syntax for passing a const pointer by const reference into a function of a templated class whose template argument is a non const pointer ie:

    template<typename T>
    struct Foo
    {
        void Add( const T& Bar ) { printf(Bar); }
    };
    
    void main()
    {
    Foo<char*> foo;
    const char* name = "FooBar"; 
    foo.Add(name);                 // Causes error
    }
    

    So I've simplified my problem here but basically I want the argument to 'Add' to have a const T ie const char*. I've tried:

    void Add( const (const T)& Bar ); 
    
    typedef const T ConstT;
    void Add( const (ConstT)& Bar );  
    
    void Add( const typename std::add_const<T>::type& Bar ); 
    

    None of which work. The exact error I'm getting is:

    error C2664: 'Foo<T>::Add' : cannot convert parameter 1 from 'const char *' to 'char *const &'
              with
              [
                  T=char *
              ]
              Conversion loses qualifiers 
    

    which I can see is correct but how do I solve it without const casting 'name' to be non const.

  • user176168
    user176168 about 11 years
    In my particular case I can't do that without a lot of code rewriting. What I really want to know is why this causes undefined behaviour. This logically (to me at least) seems to be perfectly valid and safe ie I'm passing in a pointer to an object that I don't want to modify by a reference to the pointer which I also don't want to modify and I'm not contravening either rule inside that function.
  • Benjamin Lindley
    Benjamin Lindley about 11 years
    @user176168: It won't necessarily cause undefined behavior. I said "probably", though I may have been overstating the risk. I, personally, never mess around with const cast because it can easily lead to undefined behavior, and I advise others to do the same. But if you know what you are doing, you can certainly use it without invoking undefined behavior. Just remember that others with less experience may have to maintain your code.
  • user176168
    user176168 about 11 years
    I'm aware of constness that doesn't seem to be my problem here. If for example I'm explicit about the type ie: 'void Add( char const * const & Bar )' it works fine and so does 'void Add( const char* const & Bar )'. However if I put 'const T' back in for 'const char*' or try to typedef it etc then it fails ie it seems using a template argument is failing me here for some reason.
  • Benjamin Lindley
    Benjamin Lindley about 11 years
    @user176168: The reason is explained here and in other answers. Try this: void Add(char * const & Bar) -- It doesn't work, right?
  • user176168
    user176168 about 11 years
    Ok so this partially solves my problem but I don't like it because a) I now have to have multiple functions for the scenario's where T isn't a pointer and b) it isn't really const safe ie I can now do: void Add(typename std::remove_pointer<T>::type const*& Bar ) { Bar = "Boo"; } which changes 'name' to point to "Boo" now eek! Ok so thats easily solved with a const* const & but I still have to have multiple functions.
  • user176168
    user176168 about 11 years
    @Benjamin: In my question I state I know that doesn't work I'm trying to find a solution to it.
  • Benjamin Lindley
    Benjamin Lindley about 11 years
    @user176168: Right. But you said in the previous comment: "using a template argument is failing me here for some reason." -- Indicating that you didn't know the reason it wasn't working. So I was pointing out that the reason it wasn't working is explained in this answer.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas about 11 years
    @user176168: You are not understanding the answer: I want the argument to 'Add' to have a const T ie const char*. To be a const T is not const char*, but char * const. const T applies to the *whole type T in this case char*. What you are asking is to change T to U where if T is a pointer to X, U is a pointer to const X, which can be done but have completely different semantics.
  • Captain Obvlious
    Captain Obvlious about 11 years
    Bar = "Boo" only changes the pointer value passed, not the contents of the original string. If you want to prevent this change it to const* const&. This will prevent you from modifying variable it references. OF course at that point you should probably dump the reference and pass by pointer.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas about 11 years
    @user176168: Updated how to technically get the compiler to swallow what you want, although I believe that you are much better off understanding what const means in both contexts and rethinking whether what you are asking for really makes any sense. BTW, you might want to consider std::string instead of C style strings...
  • user176168
    user176168 about 11 years
    @David: Great stuff finally got there! That is exactly what I'm asking for. I was going to answer this myself with a similar solution. If you would be so kind as to separate out your answer I'll tick it as my solution. Thanks
  • David Rodríguez - dribeas
    David Rodríguez - dribeas about 11 years
    @user176168: Note that there is a good amount of code smell in that code and the need for that signature. I would recommend that you describe what your real problem is and work a good solution from there.
  • Slav
    Slav about 9 years
    Is there standard "struct add_const_to_pointee" in std namespace? Would be surprising if there is no.