С++ char * [] to char ** преобразование

У меня есть этот простой код, который компилируется без ошибок/предупреждений:

void f(int&, char**&){}

int main(int argc, char* argv[])
{
    f(argc, argv);
    return 0;
}

И следующий аналогичный код, который не компилируется:

void f(int&, char**&){}

int main()
{
    int argc = 2;
    char* argv[] = { "", "", nullptr };
    f(argc, argv); 
    //@VS2013 error: cannot convert argument 2 from 'char *[3]' to 'char **&'
    //@GCC error: invalid initialization of non-const reference of type 'char**&' from an rvalue of type 'char**'
    return 0;
}

Почему char*[] может быть преобразован в char**& в первом примере и не может быть преобразован во втором образце? Имеет ли значение, известно ли размер во время компиляции?

EDIT: Я думаю, что во втором случае требуется 2 конверсии, и только компилятор может сделать только одно неявное преобразование.

Этот код компилируется отлично:

void f(int&, char**&){}

int main()
{
    int argc = 2;
    char* temp[] = { "", "", nullptr };
    char** argv = temp;
    f(argc, argv);
    return 0;
}

Ответы

Ответ 1

Комментарий Джефффри ссылается на стандарт, вот он:

4.2 Преобразование массива в указатель [conv.array]

Значение lvalue или rvalue типа "массив из N T" или "массив неизвестной границы T" можно преобразовать к значению типа "указатель на T". Результатом является указатель на первый элемент массива.

И значение prewue:

Значение prvalue ( "pure" rvalue) является выражением, которое идентифицирует временную объект (или его подобъект) или значение, не связанное с каким-либо объект.

Вы не можете привязать неконстантную ссылку к временному.

int& i = int(); // error

char* argv[] = { "", "", nullptr };
// the result of the conversion is a prvalue
char**& test = argv; // error

Поэтому следующий код будет с удовольствием компилироваться:

#include <iostream>

void f(int& argc, char** const& argv){
    std::cout << argv[0] << std::endl; // a
}

int main()
{
    int argc = 2;
    char* argv[] = { "a", "b", nullptr };
    f(argc, argv); 
    return 0;
}

Одна важная вещь, о которой я застеклен, отмечена в комментаре Kanze.

В первом примере, представленном в OP, char* argv[] и char** argv эквивалентны. Следовательно, преобразование отсутствует.

std::cout << std::is_array<decltype(argv)>::value << std::endl; // false
std::cout << std::is_array<char**>::value << std::endl; // false
std::cout << std::is_array<char*[]>::value << std::endl; // true
std::cout << std::is_same<decltype(argv), char**>::value << std::endl; // true
std::cout << std::is_same<decltype(argv), char*[]>::value << std::endl; // false

Ответ 2

Потому что, несмотря на внешность, второй аргумент main имеет тип char**. При использовании в качестве объявления функции аргумент, массив верхнего уровня переписывается указателю, поэтому char *[], по сути, char**. Это относится только к функции параметры, однако.

A char*[] (как в вашем втором случае) может преобразовать в char**, но результаты преобразования (как и при любом преобразовании) являются rvalue и не может использоваться для инициализации неконстантной ссылки. Почему вы хотите ссылку? Если нужно изменить указатель, изменение аргумента char** до main - это поведение undefined (формально, в C, по крайней мере, я не проверял, больше ли С++ либерал здесь). И, конечно же, вы никак не можете изменить постоянный адрес массива. И если вы не хотите изменить его, зачем использовать ссылку?

Ответ 3

Тип temp в

char* temp[] = { "", "", nullptr };

не char * [], it

char*[3]

Последнее не может быть неявно преобразовано в ` char ** '.

В main тип argv - это несвязанный массив char*, который эквивалентен char**

Я допускаю, что это запутывает:)