我注意到一个类型定义的指针上的constness失去了隐式转换为非类型定义类型的const类型的能力。
由于我显然缺乏适当的词汇来解释这个问题,我将展示一些例子。
下面的代码无法用GCC 8.3.0
编译typedef char* char_ptr;
void myFunc(const char_ptr param);
void test() {
const char str[] = "Hello, World!";
myFunc(str);
}
给出如下错误:
错误:从' const char* '到' char_ptr '{也称为' char* '}的无效转换[-fpermissive]myFunc (str);
我不是在寻找一个解决方案。我知道很多方法可以解决这个问题,比如转换类型或更改函数声明。
我想了解为什么我得到这个错误,为什么它只发生在const的类型定义的指针类型。
我已经缩小了一些用例,试图理解编译器何时认为类型相同https://ideone.com/Rk9gD9
typedef char char_t; // Char type
typedef char* char_ptr; // Char pointer
typedef const char* char_const_ptr; // Char const pointer
IS_SAME( char, char ); // Obviously true
IS_SAME( char, float ); // Obviously false
// Testing char_t
IS_SAME( char, char_t ); // true: OK
IS_SAME( const char, char_t ); // false: OK
IS_SAME( char, const char_t ); // false: OK
IS_SAME( const char, const char_t ); // true: OK
// Testing char_ptr
IS_SAME( char*, char_ptr ); // true: OK
IS_SAME( const char*, char_ptr ); // false: OK
IS_SAME( char*, const char_ptr ); // false: OK
IS_SAME( const char*, const char_ptr ); // false: Why?
IS_SAME( char* const, char_ptr ); // false: OK
IS_SAME( char* const, const char_ptr ); // true: Why?
// Testing char_const_ptr
IS_SAME( char*, char_const_ptr ); // false: OK
IS_SAME( const char*, char_const_ptr ); // true: OK
IS_SAME( char* const, char_const_ptr ); // false: OK
我可以正确地预测所有的情况,除了那些与我预期相反的is_same<const char*, const char_ptr>
和is_same<char* const, const char_ptr>
。
我对这个主题的理解是,在之前写const
一个类型定义的指针类型实际上相当于在之后写const
一个非类型定义的指针类型(匹配上述测试的结果)。我似乎没有一种好的记忆技巧来提醒自己,什么时候const类型限定符应该写在类型定义或非类型定义的类型之前或之后。
我将用下面的代码(传递静态断言)来说明typedef
和添加的const
发生了什么:
#include <type_traits>
typedef char* char_ptr;
static_assert(std::is_same_v<const char_ptr, char* const>);
// which is the same as
static_assert(std::is_same_v<char_ptr const, char* const>);
const
适用于指针,而不是它所指向的char
。const
总是应用于它的左边——除非左边没有任何东西,那么它就应用于它的右边,但是它应用于整个char_ptr
,就像通过执行char_ptr const
一样。
typedef
声明:
typedef char* char_ptr;
声明名称char_ptr
作为类型char *
的别名。也就是说,作为指向非常量char
的指针的别名。
那么,这个函数声明:
void myFunc(const char_ptr param);
将形参param
声明为指向非常量char
的常量指针。
可以重写为:
void myFunc( char * const param);
注意这一点。为了确定函数的类型,丢弃高级限定符conts
。也就是说,上面的函数声明等价于下面的声明(即不是函数定义):
void myFunc( char * param);
例如,可以这样声明函数:
void myFunc( char * param);
并定义它,例如:
void myFunc( char * const param)
{
std::cout << param << 'n';
}
或者相反,可以这样声明函数:
void myFunc( char * const param);
并定义为:
void myFunc( char * param)
{
std::cout << param << 'n';
}
在第一个函数定义中,不能改变指针本身,例如:
std::cout << ++param << 'n';
但是,在这两个函数定义中,您可以更改指针指向的字符,例如:
*param = 'A';
,因为该指针是指向非常量字符的指针,与它本身是否是常量指针无关。
另一方面,在函数test()
中,声明了一个常量字符数组:
const char str[] = "Hello, World!";
使用数组作为实参表达式,将其隐式转换为指向其第一个元素的指针,类型为const char *
。
并且,您试图将这个类型为const char *
的指针传递给函数,其对应的参数类型为char * const
。也就是说,实参表达式具有指向常量对象的类型指针,而形参表达式具有指向非常量对象的类型常量指针。
所以,编译器发出一个错误。
可以这样声明类型定义:
typedef const char* char_ptr;
然后函数声明:
void myFunc(const char_ptr param);
等价于:
void myFunc(const char * const param);
,您可以从函数test()
中调用函数,如:
void test() {
const char str[] = "Hello, World!";
myFunc(str);
}
您必须记住typedef
不是宏。
你的解释似乎认为你可以把typedef(别名)文本地替换成代码,它是一样的。这不是真的。它创建了一个新的类型别名(就像在类型周围添加括号一样)。
typedef char char_t; // Char type
typedef char* char_ptr; // Char pointer
typedef const char* char_const_ptr; // Char const pointer
另一个要记住的规则是const
绑定左。除非它在最左边,否则它会与右边结合。所以把const从最左边移开再往右一个位置是相同的类型
我总是把const
从"very left"因为它使阅读类型更容易(类型从右向左阅读)。为了进行一致的比较,您应该这样做,因为您现在有了const
的标准位置,并且可以进行文本比较。
所以我会把你的类型看成:
char_t => (char)
char_ptr => (char*)
char_const_ptr => (const char*) => (char const *)
现在看看你的问题:
IS_SAME( const char*, const char_ptr ); // false: Why?
IS_SAME( char* const, const char_ptr ); // true: Why?
const char* => char const *
char* const => char * const
const char_ptr => const (char*) => (char*) const => char * const
参见阅读类型