【问题标题】:C++ passing a const pointer by const referenceC++ 通过 const 引用传递 const 指针
【发布时间】:2013-04-22 15:22:03
【问题描述】:

嗯,VC2012 中的一个奇怪的问题我似乎无法弄清楚通过 const 引用将 const 指针传递给模板参数是非 const 指针的模板类的函数的语法,即:

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
}

所以我在这里简化了我的问题,但基本上我希望“添加”的参数有一个 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 

我可以看到是正确的,但是如何在没有 const 将 'name' 转换为非 const 的情况下解决它。

【问题讨论】:

  • 你得到什么错误信息?

标签: c++ constants


【解决方案1】:

指向常量对象的指针(T const* 或 const T*)与指向非常量对象的常量指针(T * const)之间存在很大差异。在您的情况下,成员 Add 的签名是:

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

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

const T& [T == char*]

他们误解了

const char*&

如果const放在正确的地方:

T const &

对于初学者来说事情更简单,因为简单的心理替代很有效:

char * const &

与您所问的问题不同,但也许您认为自己想要的是:

给定一个类型 T 有一个函数,如果 T 不是指针类型,则 U 是 const T,如果 T 是指向 @987654339 的指针,则 X 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 &amp;,因为您似乎想将 const&amp; 传递给某些东西,并且您还想要指出输入const。

您可能想知道元函数的名称:*add_const_here_or_there*,之所以这样是有原因的:没有简单的方法来描述您正在尝试做什么,这通常是代码异味。但在这里你有你的解决方案。

【讨论】:

  • 我知道 constness 似乎不是我的问题。例如,如果我明确说明类型,即:'void Add( char const * const & Bar )' 它可以正常工作,'void Add( const char* const & Bar )' 也是如此。但是,如果我将 'const T' 放回 'const char*' 或尝试对它进行 typedef 等,那么它会失败,即似乎由于某种原因使用模板参数在这里失败了。
  • @user176168:原因在此处和其他答案中进行了解释。试试这个:void Add(char * const &amp; Bar) -- 不行,对吧?
  • @Benjamin:在我的问题中,我说我知道这行不通,我正在努力寻找解决方案。
  • @user176168:对。但是您在之前的评论中说过:“由于某种原因,我在这里使用模板参数失败了。” -- 表示您不知道它不起作用的原因。所以我指出,这个答案解释了它不起作用的原因。
  • @user176168:您不理解答案:我希望“添加”的参数有一个const T,即const char*。成为const T不是const char*,而是char * const。 const T 适用于 *whole 类型 T 在本例中为 char*。你要问的是将T 更改为U,其中如果T 是指向X 的指针,U 是指向const X 的指针,可以这样做,但语义完全不同。跨度>
【解决方案2】:

这里看起来像您的问题,因为一旦您将指针类型映射到模板类型,您就不能再将 const-ness 添加到指向的类型,只能添加到指针本身。看起来您正在尝试做的是自动将 constness 添加到函数的参数中(因此,如果 T 是 char*,则函数应该接受 const char* const&amp; 而不是 char* const&amp; 如您所写的那样)。做到这一点的唯一方法是使用另一个模板为指针类型的指针添加常量,如下所示。我冒昧地添加了丢失的标题并更正了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”?如果没有的话会很奇怪。
【解决方案3】:

您需要将 Foo 对象的模板参数更改为 Foo&lt;const char*&gt;。因为如果T=char*,那么const T=char*const,而不是const char*。试图强制它工作不是一个好主意,并且可能会导致未定义的行为。

【讨论】:

  • 在我的特殊情况下,如果不重写大量代码,我就无法做到这一点。我真正想知道的是为什么这会导致未定义的行为。这在逻辑上(至少对我而言)似乎是完全有效和安全的,即我正在传递一个指向我不想通过引用我也不想修改的指针来修改的对象的指针,并且我没有违反该函数中的任何一条规则。
  • @user176168:它不一定会导致未定义的行为。我说“可能”,尽管我可能夸大了风险。我个人从不乱用 const cast,因为它很容易导致未定义的行为,我建议其他人也这样做。但是如果你知道你在做什么,你当然可以使用它而不调用未定义的行为。请记住,经验较少的其他人可能必须维护您的代码。
【解决方案4】:

用途:

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

写int main()而不是void main()

【讨论】:

    【解决方案5】:

    如果将const char* 而不是char* 传递给Foo 不是一个选项,您可以使用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::decay 和 std::remove_pointer

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

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

    【讨论】:

    • 好的,所以这部分解决了我的问题,但我不喜欢它,因为 a) 我现在必须有多个函数用于 T 不是指针的场景,b) 它不是真的const 安全,即我现在可以这样做: void Add(typename std::remove_pointer::type const*& Bar ) { Bar = "Boo";现在将“名称”更改为指向“嘘”!好的,这很容易用 const* const & 解决,但我仍然必须有多个函数。
    • Bar = "Boo" 只改变传递的指针值,不改变原始字符串的内容。如果您想阻止这种情况,请将其更改为 const* const&amp;。这将阻止您修改它引用的变量。当然,此时您可能应该转储引用并通过指针传递。
    猜你喜欢
    • 1970-01-01
    • 2021-02-28
    • 1970-01-01
    • 2012-06-14
    • 2021-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    相关资源
    最近更新 更多