猜测:数组参数传递时退化为指针,可以认为是隐式转换。而值到引用的也可以认为是对应一次隐式转换。二者如何都需要做,编译就会失败。因为隐式转换只能一次。

场景

在main函数中的两个参数(int argc , char *argv[]),都是系统构造的。通常来说,我们只需要去解析即可,不需要去构造这样一个参数。

然而,今天写代码时却不得不构造这样一个参数。原因是使用了一个第三方的模块(准确的说是基于第三方模块),第三方模块初始化时需要接受(int argc , char ** &argv)这样的参数。注意到其中的引用符号!

通常来说,直接传递main函数的argc , argv是没有问题的。事实也确实如此,传递main函数接受的argv不会有编译错误。不过有些坑就是,这个模块自己参数解析写得太武断!就是不允许有多余的参数项!一旦有多余参数项就会导致其停止解析参数,最终导致其解析失败。然而我的程序除了给这个模块传参数,还需要额外的参数啊!

没有办法,我就得自己构造仅仅包含第三方模块需要的参数argv , 但是狗血的事情出现了!

怎么构造就是不行!!编译一直报错:

用类型为‘char**’的右值初始化类型为‘char**&’的非常量引用无效

经过一系列的修改,终于正确编译了。

一切的狗血,都是因为概念理解的不清晰。

解决方案

只考虑argv的构造:

int fack_main(int argc , char **&argv);

int fack_argc = 2 ;
char arg0[] = "program name" ;
char arg1[] = "arg1 for thirdparth" ;
char **fake_argv = new char *[fack_argc+1]{ arg0 , arg1 } ;

fack_main(fack_argc , fack_argv) ; 

delete [] fack_argv ;

以上就是完整的构造过程。这样构造的fack_argv应该与main函数中的argv完全等价的。

解决过程

解决过程就很辛酸了。

  1. raw

     fack_argv[2][100] { "program name" , "arg1"  } ;
    
     fack_main(fack_argc , fack_argv) ; 
    

    报错:

     用类型为‘char (*)[100]’的右值初始化类型为‘char**&’的非常量引用无效
    

    看到这个错误还是很好理解的。说明参数传递时fack_argv还是是一个指向char[100]的指针类型char (*)[100],不是char **&

    想到的解决办法是static_cat

  2. static_cast

     fack_argv[2][100] { "program name" , "arg1" } ;
     fack_main(fack_argc , static_cast<char **>(fack_argv)) ; 
    

    结果报错:

     从类型‘char [3][100]’到类型‘char**’中的 static_cast 无效
    

    为何强制转换会失效呢?

    难道只能一步步转换?

    试了以下代码:

     fack_main(argc , static_cast<char **>(static_cast<char(*)[100]>(f_argv))) ; 
    

    想要通过两步完成,但是依然报错:

     错误:从类型‘char (*)[100]’到类型‘char**’中的 static_cast 无效
    

    这说明从一维数组到指针是OK的,但是第二步就失败了。一想,第二步时,代码其实是在将一个指向数组的指针转为一个指向指针的指针。这或许是编译器不支持的。

    失败之后,再一想,既然直接声明二维数组失败了,那么看来只有声明一个一维的数组,数组里放char *就好了。

  3. 指向指针(char*)的数组

     char arg0[] = "program name" ;
     char arg1[] = "arg1" ;
     char *fake_argv[2] = { arg0 , arg1  } ;
    

    结果还是报错:

     错误:用类型为‘char**’的右值初始化类型为‘char**&’的非常量引用无效
    

    这次需要注意了,前面已经对了——我们已经构造了char**,无奈目标是 char **& 。但是奇怪的是,传main的argv就不会报错,那说明这二者间还是有差别的!

    需要继续改!

    这次着眼于后边部分——”非常量引用”!

    是不是说char **可以转换为常量引用啊?

  4. 测试:将char *[] 转为常量引用

    鼓捣了半天,终于弄明白常量引用如何声明:

     char *fake_argv[2] = { arg0 , arg1  } ;
     char ** const (&ra) = fake_argv ;
    

    通过!

    从上面可以看出,所谓常量引用,是要求一个底层const,就是说ra的值不能改变。

    这个测试对问题而言帮助不大,但是隐式地说明——还是变量类型声明出了问题。

    网上搜索!

  5. 网上搜索argv *[]

    查看 Defining own main functions arguments argc and argv

    发现了问题是相似的,但是提供的解决办法依然不能解决形参是car **&的问题。

    不过收获到:argv最后要多一个NULL结束标志

  6. 完全的类型一致!

    既然是说char **char **&错误,但是我的实参依然是个数组,这说明编译器已经做了隐式转换。

    我记得在类的构造函数中说过,类的隐式转换最多一次。

    有没有这种可能:

    值参数到引用参数,其实也是一个隐式转换

    再加上前面的猜测——最多一次隐式转换,问题就明白了。一次隐式转换用来将char *argv[]变为char **argv,所以就导致了char **char **&的失败。

    书读得不多,我觉得应该会有相关介绍的。这里只是猜测。

    这样,就有了最后的方案:

     int fack_main(int argc , char **&argv);
    
     int fack_argc = 2 ;
     char arg0[] = "program name" ;
     char arg1[] = "arg1 for thirdparth" ;
     char **fake_argv = new char *[fack_argc+1]{ arg0 , arg1 } ;
    
     fack_main(fack_argc , fack_argv) ; 
    
     delete [] fack_argv ;
    

    通过编译!问题解决。

技术文章将同时发布到CSDN博客。