C++ 通过常量引用传递常量指针

6

在VC2012中有一个奇怪的问题,我似乎无法理解将常量指针通过常量引用传递到模板类函数中,而该模板参数为非常量指针的语法:

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
}

我在这里简化了我的问题,但基本上我想让“Add”参数具有const T,即const char *。 我尝试过:

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 ); 

这些方法都不起作用。我得到的确切错误信息是:

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

我可以看到这是正确的,但我如何在不将“name”强制转换为非const的情况下解决它。


你收到了什么错误信息? - Ali
5个回答

9

指向常量对象的指针(T const*const T*)与指向非常量对象的常量指针(T * const)之间存在明显的区别。在您的情况下,成员函数 Add 的签名为:

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

出于这个原因,我通常建议人们放弃在左侧使用const,因为初学者通常会将typedef(或推导类型)与类型替换混淆,当他们阅读到:

const T& [T == char*]

他们误解了。
const char*&

如果const被放在正确的位置:
T const &

对于初学者来说,事情变得更简单了,因为纯粹的心理替换就可以解决问题:

char * const &

虽然不是你所问的问题,但也许是你想要的解决方案:

对于给定类型 T,拥有一个函数,该函数接受一个参数 U,如果 T 不是指针类型,则 Uconst T,如果 T 是指向 X 的指针,则 UX const *

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;
};

那么您可以在签名中使用这个:
template <typename T>
void Foo<T>::Add( const typename add_const_here_or_there<T>::type & arg ) {
     ...

请注意,我在函数签名中添加了两个const关键字,因此在您的情况下,char*将映射到char const * const &,因为似乎您想要将const&传递给某些东西,并且您还希望指向的类型为const
也许您已经想知道元函数*add_const_here_or_there*的名称是如何得来的,这是有原因的:通常没有简单的方法来描述您正在尝试做什么,这通常是代码异味。但是现在您已经有了解决方案。

我知道const的问题,但这似乎不是我的问题所在。例如,如果我明确指定类型,比如'void Add( char const * const & Bar )',它可以正常工作,'void Add( const char* const & Bar )'也是如此。然而,如果我将'const T'放回到'const char*'中,或者尝试对其进行typedef等操作,则会失败,即在这里使用模板参数似乎让我失望了。 - user176168
1
@Benjamin:在我的问题中,我已经说明了那种方法行不通,我正在尝试找到解决方案。 - user176168
@user176168:没错。但你在之前的评论中说:“在这里使用模板参数有些问题。”-- 这表明你不知道它不能正常工作的原因。所以我指出了这个答案中解释了它不能正常工作的原因。 - Benjamin Lindley
1
@user176168:您没有理解答案:*我希望'Add'的参数具有const T,即const char *const T不是const char *,而是char * const。在这种情况下,const T适用于整个类型T,即char *。您要求更改TU,其中如果T是指向X的指针,则U是指向const X的指针,这是可以实现的,但具有完全不同的语义。 - David Rodríguez - dribeas
@David:太棒了,终于搞定了!这正是我所要求的。我本来打算用类似的解决方案自己回答这个问题。如果你能把你的答案单独分离出来,我会将其选为我的解决方案。谢谢。 - user176168
显示剩余3条评论

3

看起来你的问题是,一旦你有一个指针类型映射到一个模板类型,你就不能再给指向的类型添加const性,只能给指针本身添加。你似乎想自动将constness添加到函数参数中(所以如果T是char*,函数应该接受const char* const&而不是你写的char* const&)。唯一的方法是使用另一个模板为指针类型的指向者添加const性,如下所示。我有权包括缺失的头文件并更正了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
}

正如在另一个地方提到的一样,如果您使用std::string而不是C风格字符串,则可以轻松解决此问题。


在std命名空间中是否有标准的“struct add_const_to_pointee”?如果没有,那将是令人惊讶的。 - Slav

1
如果不能将char*替换为const char*,则可以使用std::remove_pointer来优化正确的类型。这将删除指针修饰符,并允许您提供更明确的类型。
#include <type_traits>

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

为了防止指针值被修改,您也可以将引用声明为const
void Add(typename std::remove_pointer<T>::type const* const& Bar )
{ Bar = "name"; } // <- fails

如果您需要将类型从指向指针的类型降级,您可以使用std::decaystd::remove_pointer。请注意,保留HTML标签。
void Add(typename std::remove_pointer<typename std::decay<T>::type>::type const*& Bar)
{
    printf(Bar);
}

这主要取决于您对 T 的要求。我建议假设只传递基本类型(例如char),并从此构建引用和指针类型。

好的,这部分解决了我的问题,但我不喜欢它,因为a) 现在我必须对T不是指针的情况有多个函数; b) 它实际上并不是const安全的,即现在我可以做:void Add(typename std::remove_pointer <T> ::type const Bar){Bar =“Boo”; } ,这将更改'name'指向“Boo”现在eek!好的,所以用const * const &很容易解决,但我仍然必须有多个函数。 - user176168
Bar = "Boo" 只会改变传递的指针值,而不是原始字符串的内容。如果您想防止这种更改,请将其更改为 const* const&。这将防止您修改它所引用的变量。当然,在那时,您应该放弃引用并通过指针传递。 - Captain Obvlious

1
你需要将模板参数更改为Foo<const char*>,因为如果T=char*,那么const T=char*const,而不是const char*。试图强制其工作并不是一个好主意,并且可能导致未定义的行为。

1
在我的情况下,如果不进行大量代码重写,我无法做到这一点。我真正想知道的是为什么这会导致未定义的行为。在逻辑上(至少对我来说),这似乎是完全有效和安全的,即我通过指向一个对象的指针传递给一个引用,我不想修改该对象,也不想修改指针的引用,并且我没有违反该函数内部的任何规则。 - user176168
@user176168:这不一定会导致未定义的行为。我说“可能”,虽然我可能夸大了风险。个人而言,我从不玩弄const cast,因为它很容易导致未定义的行为,并建议他人也不要这样做。但是如果您知道自己在做什么,可以在不引发未定义行为的情况下使用它。只需记住,其他经验不足的人可能需要维护您的代码。 - Benjamin Lindley

1

使用:

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

而不是 void main()

,请写成 int main()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接